#---------------------------------------------------------------------------------------------------------------------------- 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.) Son Güncelleme: 14/04/2025 - Pazartesi #---------------------------------------------------------------------------------------------------------------------------- #---------------------------------------------------------------------------------------------------------------------------- 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. #---------------------------------------------------------------------------------------------------------------------------- #---------------------------------------------------------------------------------------------------------------------------- Kursumuzda Python kodlarında yazım stiili olarak PEP 8 ("Style Guide for Python Code") kullanmayacağız. Örneğin hangi fonksiyonların ve sınıfların hangi modüllerin içerisinde olduğunun daha kolay kav iranabilmesi çin import işlemlerini dosyanın başında yapmak yerine kullanım yerine yakın bir yerde yapacağız. Bu nedenle kursumuzdaki notlarda PEP 8’e uygun olmayan kodlar gördüğünüzde bu kodların kasten bu biçimde düzenlendiğini düşünmelisniz. Tabii bu kodları kolay bir biçimde PEP 8'e uygun hale getirebilirsiniz. #---------------------------------------------------------------------------------------------------------------------------- #---------------------------------------------------------------------------------------------------------------------------- 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 açı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 bilişsel 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 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 zekanın genel bir tanımı şöyle yapılabilir: "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. - Yapay Zeka 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 1950’li 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 1950’lerde 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: 1930’larda Alonzo Church "Lambda Calculus" denilen biçimsel sistemi geliştirmiş ve özyinelemeli fonksiyonel notasyonla hesaplanabilirliği araştırmış ve sorgulamıştır. Yine 1930’larda Kurt Gödel "biçimsel sistemler (formal systems)" üzerindeki çalışmalarıyla teorik bilgisayar bilimlerinin öncülüğünü yapmıştır. - Turing Makineleri: Alan Turing’in henüz elektronik bilgisayarlar gerçekleştirilmeden önce 1930’lu 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ı: 1940’lı 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 College’de 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 Lisp’i 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 çeşitli problemlere 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 finansman 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 finansman 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ı yeniden 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. 2017 yılından itibaren "dönüştürücülerin (transformers)" ve "büyük dil modllerinin (large language models)" ortaya çıkması yaşamın her alanında devrim niteliğinde etkilerin ortaya çıkmasına yol açmıştır. Artık dünya yepyeni bir aşamaya evrilmektedir. #---------------------------------------------------------------------------------------------------------------------------- #---------------------------------------------------------------------------------------------------------------------------- Yapay zeka aslında pek çok alt konuya ayrılabilen bir alandır. Yapay zekanın önemli alt alanları şunlardır: - Makine Öğrenmesi - Yapay Sinir Ağları ve Derin Öğrenme - Robotik Sistemlerin Tasarımı ve Gerçekleştirilmesi - Bulanık Sistemler (Fuzzy Logic Systems) - Evrimsel Yöntemler (Genetic Algoritmalar, Differential Evoluation, Neuroevolution vs. ) - Üst Sezgisel (Meta Heuristic) Yöntemler (Karınca Kolonisi, Particle Swarm Optimization) - Olasılıksal (Probabilistic) Yöntemler (Bayesian Network, Hidden Markov Model, Kalman Filter vs.) - Uzman Sistemler (Expert Systems) #---------------------------------------------------------------------------------------------------------------------------- #---------------------------------------------------------------------------------------------------------------------------- Yapay zekanın yukarıdaki uygulama alanlarından bazıları zaman içerisinde bazı kesimler tarafından artık yapay zekanın bir konusu olarak görülmemeye başlanmıştır. Örneğin OCR işlemleri eskiden her kesim tarafından bir yapay zeka faaliyeti olarak görülürdü. Ancak zamanla OCR işlemleri o kadar bilindik ve rutin bir hale geldi ki artık bu işlemler geniş bir kesim tarafından bir yapay zeka faaliyeti olarak ele alınmıyor. Benzer biçimde satrançta bir büyük ustayı yenecek programın yazılması eskiden bir yapay zeka faaliyeti olarak görülüyorken artık bu faaliyet de bazı kesimler tarafından bir yapay zeka faaliyeti olarak ele alınmamaktadır. İşte zaman içerisinde "yapay zeka olarak görülen bir süreci makineler algoritmik olarak başardıkça onun yapay zeka alanından çıkartılması" gibi bir durum oluşmaya başlamıştır. Eskiden yapay zeka faaliyeti olarak adlandırılan bazı faaliyetlerin yapay zeka kapsamından çıkarılmasına "Yapay Zeka Etkisi (AI Effect)" denilmektedir. Günümüzde de yapay zeka faaliyeti olarak ele aldığımız bazı faaliyetlerin zamanla yapay zeka faaliyeti olmaktan çıkabileceğine de dikkatinizi çekmek istiyoruz. #---------------------------------------------------------------------------------------------------------------------------- #---------------------------------------------------------------------------------------------------------------------------- Şimdi de makine öğrenmesinin ne anlama geldiği üzerinde duracağız. Ancak makine öğrenmesinin ne olduğundan önce öğrenmenin ne olduğunu ele almak gerekir. Psikolojide öğrenme "davranışta göreli biçimde kalıcı değişiklikler oluşturan süreçler" biçiminde tanımlanmaktadır. Bu tanımdaki davranış (behavior) klasik davranışçılara göre "gözlemlenebilen devinimleri" kapsamaktadır. Ancak daha sonra "radikal davranışçılar" bu davranış tanımını zihinsel süreçleri de kapsayacak biçimde genişletmiştir. Psikolojide öğrenme kabaca dört bölümde ele alınmaktadır: 1) Klasik Koşullanma (Classical Conditioning) 2) Edimsel Koşullanma (Operant Conditioning) 3) Sosyal Bilişsel Öğrenme (Social Cognitive Learning) 4) Bilişsel Öğrenme (Cognitive Learning) #---------------------------------------------------------------------------------------------------------------------------- #---------------------------------------------------------------------------------------------------------------------------- 11. Ders - 03/02/2024 - Cumartesi #---------------------------------------------------------------------------------------------------------------------------- #---------------------------------------------------------------------------------------------------------------------------- Organizmada doğal bir tepki oluşturan uyaranlara "koşulsuz uyaranlar (unconditioned stimuli)", koşulsuz uyaranlara karşı organizmanın verdiği tepkilere de "koşulsuz tepkiler (unconditioned responses)" denilmektedir. Örneğin "gök gürültüsü" koşulsuz uyarana, gök gürültüsüne karşı verilen tepki de koşulsuz tepkiye örnek verilebilir. İşte klasik koşullanmada başlangıçta organizmada bir tepkiye yol açmayan "nötr bir uyaran (neutral stimulus)" koşulsuz bir uyaranla (unconditioned stimulus) zamansal bakımdan eşleştirildiğinde artık bu nötr uyaran organizmada koşulsuz uyaranın oluşturduğu tepkiye benzer bir tepki oluşturmaya başlamaktadır. Nötr uyaranın zamanla koşulsuz uyaranla benzer tepkilere yol açması durumunda artık bu nötr uyarana "koşullu uyaran (conditioned stimulus)", organizmanın da bu koşullu uyarana verdiği tepkiye "koşullu tepki (conditioned response)" denilmektedir. Bu süreci şekilsel olarak aşağıdaki gibi ifade edebiliriz: Koşulsuz Uyaran ---> Koşulsuz Tepki (Doğal Durum) Nötr Uyaran ---> Koşulsuz Uyaran (Klasik Koşullanma Süreci) Nötr Uyaran ---> Koşulsuz Uyaran (Klasik Koşullanma Süreci) ... ---> ... (Klasik Koşullanma Süreci) Nötr uyaran artık koşullu uyaran haline gelmiştir: Koşullu Uyaran ---> Koşullu Tepki (Öğrenilmiş Yeni Durum) Klasik koşullanma sürecinin gerçekleşmesi için önce nötr uyaranın sonra koşulsuz uyaranın zamansal bakımdan peş peşe uygulanması gerekmektedir. Burada iki uyaran arasındaki zaman uzarsa (örneğin 5 saniyeden büyük olursa) organizmanın onları ilişkilendirmesi güçleşmektedir. Pek çok çalışma 0.5 saniye civarındaki bir zaman aralığının klasik koşullanma için ideal bir zaman aralığı olduğunu göstermektedir. Klasik koşullanma ilk kez Ivan Pavlov tarafından fark edilmiş ve tanımlanmıştır. Pavlov'un köpek deneyi klasik koşullanmaya tipik bir örnektir. Bu deneyde Pavlov köpeğe yemek gösterdiğinde (koşulsuz uyaran) köpek salya salgılamaktadır. Yemeği görünce köpeğin salya salgılaması doğal olan koşulsuz bir tepkidir. Daha sonra Pavlov köpeğe bir metronum sesinden (nötr uyaran) sonra yemeği göstermiş ve bu işlemi bir süre tekrarlamıştır. Bu süreç sonunda artık köpek yalnızca metronom sesini duyduğunda (artık nötr uyaran koşullu uyaran haline gelmiştir) salgı salyalar hale gelmiştir. Köpeğin metronom sesini duyduğunda salgı salgılaması daha önce yapmadığı koşullu bir tepkidir. Klasik koşullanma süreci pek çok hayvan üzerinde denenmiştir. Hayvanların çok büyük çoğunluğu klasik koşullanma ile öğrenebilmektedir. Birinci kuşak davranışçılar pek çok davranışın nedenini klasik koşullanmayla açıklamışlardır. Gerçekten de fobilerin çoğunda klasik koşullanmanın etkili olduğu görülmektedir. Özellikle olumsuz birtakım sonuçlar doğuran uyaranlar çok kısa süre içerisinde klasik koşullanmaya yol açabilmektedir. Örneğin karanlık bir sokakta saldırıya uğrayan bir kişi yeniden karanlık bir sokağa girdiğinde klasik koşullanma etkisiyle yeniden saldırıya uğrayacağı hissine kapılabilmektedir. #---------------------------------------------------------------------------------------------------------------------------- #---------------------------------------------------------------------------------------------------------------------------- Edimsel koşullanma en önemli öğrenme yollarından biridir. Pek çok süreç edimsel koşullanma ile öğrenilmektedir. Klasik koşullanmada önce uyaran sonra tepki gelmektedir. Halbuki edimsel koşullanmada önce tepki sonra uyaran gelir. Organizma bir faaliyette bulunur. Bunun sonucunda hoşa giden bir durum (buna ödül de denilmektedir) oluşursa bu davranış tekrarlanır ve böylece öğrenme gerçekleşir. Yani özetle organizmada hoşa giden sonuçlar doğuran davranışlar tekrarlanma eğilimindedir. Edimsel koşullanma bir süreç olarak ilk kez Edward Thorndike tarafından fark edilmiştir. Thorndike "puzzle box" ismini verdiği bir kafes düzeneği ile yaptığı deneylerden "organizmada olumlu sonuçlar doğuran tepkilerin tekrarlanma eğiliminde olduğu" sonucunu çıkarmıştır ve bu durumu "etki yasası (law of effect)" terimiyle ifade etmiştir. Her ne kadar edimsel koşullanmayı gerçek anlamda ilk kez Thorndike fark ettiyse de bunu genişleterek kuramsal bir öğrenme modeli haline getiren asıl kişi B.F. Skinner olmuştur. Bu konuda kullanılan terminoloji de büyük ölçüde Skinner tarafından oluşturulmuştur. Edimsel koşullanmada ödül oluşturan uyaranlara "pekiştireç (reinforcer)" denilmektedir. Davranış ne kadar pekiştirilirse o kadar iyi öğrenilmektedir. Pekiştireçler "pozitif" ve "negatif" olmak üzere ikiye ayrılmaktadır. Pozitif pekiştireçler doğrudan organizmanın hoşuna gidecek uyaranlardır. Negatif pekiştireçler ise organizmanın içinde bulunduğu hoş olmayan durumu ortadan kaldırarak dolaylı ödül oluşturan uyaranlardır. Edimsel koşullanma için bazı örnekler şöyle verilebilir: - Ödevini yapan öğrenciye öğretmenin ödül vermesi ödev yapma davranışını artırmaktadır (pozitif pekiştireç). - Maddenin bunaltıyı (anxiety) ortadan kaldırması kişiyi madde kullanımına teşvik etmektedir (negatif pekiştireç). - Arabada emniyet kemeri bağlı değilken rahatsız edici bir ses çıkmaktadır. Bu sesi ortadan kaldırmak için sürücü ve yolcular emniyet kemerini bağlarlar (negatif pekiştireç). - Ağlayan çocuğun isteklerini ebeveynin karşılaması istekleri karşılanmayan çocukta ağlama davranışını artırabilmektedir (pozitif pekiştireç). Bugün psikolojide edimsel koşullanma davranışı değiştirmede ve şekillendirmede en önemli araçlardan biri olarak kabul edilmektedir. #---------------------------------------------------------------------------------------------------------------------------- #---------------------------------------------------------------------------------------------------------------------------- Sosyal bilişsel öğrenmeye 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): Bilgiişlem ve programlama etkinlikleriyle ilgili geniş kapsamlı bir bilim dalıdır. Bilgisayar bilimlerindeki teknikler ve yöntemler kullanılmadan makine öğrenmesi uygulamaları gerçekleştirilememektedir. İ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: - 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'u 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 insanları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 standart 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 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. 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 Python 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 1’dir. Pekiyi neden standart sapma varken ayrıca onun karesi için varyans terimi uydurulmuştur? İşte istatistikte pek çok durumda aslında doğrudan ortalamadan farkların karesel ortalamaları (yani standart sapmanın karesi) kullanılmaktadır. Bu nedenle bu hesaba ayrı bir isim verilerek anlatımlar kolaylaştırılmak istenmiştir. 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 #---------------------------------------------------------------------------------------------------------------------------- Varyans hesaplamanın alternatif bir yolu daha vardır. Varyans değerlerin karelerinin ortalamasının değerlerin ortalamasının karesinden çıkartılmasıyla da hesaplanabilmektedir: varyans = değerlerin karelerinin ortalaması - değerlerin ortalamasının karesi Varyansın bu formülü tek geçişte (single pass) varyans hesaplamakta çokça kullanılmaktadır. Klasik formülle varyans hesaplamak için çift geçiş gerekmektedir. Ancak bu yöntemde bir noktaya da dikkat etmek gerekir. Değerlerin kareleri çok büyümektedir. Bu durumda çok fazla büyük değerin bulunduğu veri kümelerinde karelerinm toplamı kullanılan veri türünün limitleri dışına çıkabilir ya da yuvarlama hatalarından kaynaklanan sorunlar ortaya çıkabilir. Aşağıda tek geçiş ile bu biçimde vartyans hesabına bir örnek verilmiştir. #---------------------------------------------------------------------------------------------------------------------------- total = 0 total_square = 0 count = 0 for x in a: total += x total_square += x ** 2 count += 1 v = total_square / count - (total / count) ** 2 print(v) #---------------------------------------------------------------------------------------------------------------------------- 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 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 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 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 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ğrinin ş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ış rastgele 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.11’den ö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'un statistics modülündeki NormalDist sınıfı vektörel işlemler yapamamaktadır. Maalesef NumPy ve Pandas kütüphanelerinde normal dağılım üzerinde vektörel işlem yapan öğeler yoktur. Ancak SciPy kütüphanesi içerisinde pek çok dağılım üzerinde vektörel işlemler yapan sınıflar bulunmaktadır. Bu nedenle pratikte Python kütüphanesi yerine bu tür işlemler için SciPy kütüphanesi tercih edilmektedir. #---------------------------------------------------------------------------------------------------------------------------- #---------------------------------------------------------------------------------------------------------------------------- scipy.stats modülü içerisindeki "norm" isimli singleton nesne normal dağılım üzerinde vektörel işlem yapan metotlara sahiptir. norm bir sınıf nesnesidir ve zaten yaratılmış bir biçimde bulunmaktadır. Dolayısıyla programcı doğrudan bu nesne ile ilgili sınıfın metotlarını çağırabilir. Genellikle programcılar bu tür nesneleri kullanmak için "from import" deyimini tercih ederler: from scipy.stats import norm norm nesnesine ilişkin sınıfın cdf isimli metodu üç parametre almaktadır: cdf(x, loc=0, scale=1) Buradaki x bir NumPy dizisi ya da Python dolaşılabilir nesnesi olabilir. Bu durumda tüm x değerlerinin kümülatif olasılıkları hesaplanıp bir NumPy dizisi olarak verilmektedir. Burada loc ortalamayı, scale ise standart sapmayı belirtmektedir. Örneğin: result = norm.cdf([100, 130, 140], 100, 15) print(result) #---------------------------------------------------------------------------------------------------------------------------- from scipy.stats import norm result = norm.cdf([100, 130, 140], 100, 15) print(result) #---------------------------------------------------------------------------------------------------------------------------- norm nesnesinin ilişkin olduğu sınıfın ppf (percentage point function) isimli metodu cdf işleminin tersini yapmaktadır. Yani kümülatif olasılığı bilindiği durumda bize bu kümalatif olasılığa karşı gelen x değerini verir. (Yani ppf NormalDist sınıfındaki inv_cdf metoduna karşılık gelmektedir.): ppf(q, loc=0, scale=1) ppf (percentage point function) ismi size biraz tuhaf gelebilir. Bu isim birikimli dağılım fonksiyonunun tersini belirtmek için kullanılmaktadır. ppf aslında "medyan (median)" kavramının genel biçimidir. Anımsanacağı gibi medyan ortadan ikiye bölen noktayı belirtiyordu. Örneğin standart normal dağılımda medyan 0'dır. Yani ortalamaya eşittir. İstatistikte tam ortadan bölen değil de diğer noktalardan bölen değerler için "percentage point" de denilmektedir. Örneğin normal dağılımda 1/4 noktasından bölen değer aslında birikimli dağılm fonksiyonunun 0.25 için değeridir. #---------------------------------------------------------------------------------------------------------------------------- from scipy.stats import norm result = norm.ppf([0.50, 0.68, 0.95], 100, 15) print(result) #---------------------------------------------------------------------------------------------------------------------------- norm nesnesinin ilişkin olduğu sınıfın pdf (probability density function) isimli metodu yine x değerlerinin Gauss eğrisindeki y değerlerini vermektedir. Metodun parametrik yapısı şöyledir: pdf(x, loc=0, scale=1) 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() #---------------------------------------------------------------------------------------------------------------------------- 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 } 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 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) 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 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. 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. 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 ö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. #---------------------------------------------------------------------------------------------------------------------------- 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') plt.legend(['Standart Normal Dağılım', 't Dağılımı (Serbestlik Derecesi = 5)', 't dağılımı (Serbestlik Derecesi = 30)']) plt.show() #---------------------------------------------------------------------------------------------------------------------------- Tabii standart normal dağılımla t dağılımının olasılık yoğunluk fonksiyonları farklı olduğuna göre aynı değerlere ilişkin kümülatif olasılık değerleri de farklı olacaktır. #---------------------------------------------------------------------------------------------------------------------------- from scipy.stats import norm, t x = [-0.5, 0, 1, 1.25]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] #---------------------------------------------------------------------------------------------------------------------------- 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 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ı 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 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: 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 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, 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. 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: 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. 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 #---------------------------------------------------------------------------------------------------------------------------- #---------------------------------------------------------------------------------------------------------------------------- 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 plt.title('Lamda = 10 İçin Poisson Dağılımı', fontweight='bold', pad=10) x = range(0, 40) 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 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. ---------------------------------------------------------------------------------------------------------------------------- #---------------------------------------------------------------------------------------------------------------------------- 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. Binom dağılımının olasılık kütle fonksiyonu kombinasyon hesabıyla basit bir biçimde oluşturulabilir. Yukarıda bahsettiğimiz paranın 5 kez atılması durumunda 3 Tura gelmesi aşağıdakiler gibi olabilir: T Y T Y T T T T Y Y Y T T Y T ... Burada toplamda 3 kez Tura gelme durumu aslında C(5, 3) kadardır. Bu olaylar bir arada gerçekleşeceğine göre bu değerle p^3 değerini çarpmamız gerekir. Öte yandan Tura gelmeyen olasılıkların toplamı de 5 - 3 = 2 kadardır. O halde bunun da (1 - p)^2 ile çarpılması gerekir. Binom dağılımının olasılık kütle fonksiyonu şöyledir: P{X = x} = C(n, x)p^x * (1 - p)^(n - x) Binom dağılımı çin SciPy kütüphanesindeki stats modülünde bulunan binom isimli singleton nesne kullanılabilir. Nesnenin kullanılması diğer singleton nesnelere benzemektedir. Örneğin ilgili sınıfın pmf fonksiyonu olasılık kütle fonksiyonunu belirtmektedir. Bu fonksiyonlarda birinci parametre "olumlu gerçekleşme sayısını", ikinci parametre "toplam deney sayısını" ve üçüncü parametre de "olumlu gerçekleşme 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. 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. 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 POPULATION_RANGE = 1_000_000 NSAMPLES = 100_000 SAMPLE_SIZE = 50 samples = np.random.randint(0, POPULATION_RANGE + 1, (NSAMPLES, SAMPLE_SIZE)) samples_means = np.mean(samples, axis=1) 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 POPULATION_RANGE = 1_000_000 NSAMPLES = 100_000 SAMPLE_SIZE = 50 samples_means = [statistics.mean(random.sample(range(POPULATION_RANGE + 1), SAMPLE_SIZE)) for _ in range(NSAMPLES)] 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() #---------------------------------------------------------------------------------------------------------------------------- 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 bu durum aşağıdaki gibi ifade edilmektedir: 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 ö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 POPULATION_RANGE = 1_000_000 NSAMPLES = 100_000 SAMPLE_SIZE = 50 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}') #---------------------------------------------------------------------------------------------------------------------------- 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. 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. 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 POPULATION_RANGE = 1_000_000 NSAMPLES = 100_000 SAMPLE_SIZE = 50 population_mean = np.mean(range(POPULATION_RANGE + 1)) population_std = np.std(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) 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 anakütle 100 elemandan oluşuyor olsun. Biz 30 elemanlı örnekler çekersek bu düzeltme faktörünü kullanmalıyız. Tabii paratikte genellikle n / N değeri 0.05'ten oldukça küçük olma eğilmindedir. #---------------------------------------------------------------------------------------------------------------------------- #---------------------------------------------------------------------------------------------------------------------------- Yukarıdaki örneklerde anakütlenin normal dağılmadığına düzgün (uniform) dağıldığına dikkat ediniz. Çünkü biz 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 POPULATION_RANGE = 1_000_000 population = range(0, POPULATION_RANGE + 1) 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. #---------------------------------------------------------------------------------------------------------------------------- #---------------------------------------------------------------------------------------------------------------------------- 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ı açı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. 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. 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. 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 fonksiyonuna sokulmuştur. Burada birinci örnek için p değeri 1’e yakın, ikinci örnek için p değeri 0’a ç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) result_norm = kstest(sample_norm, 'norm', args=(100, 15)) sample_uniform = uniform.rvs(100, 100, size=1000) result_uniform = kstest(sample_uniform, 'norm', args=(100, 15)) 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() #---------------------------------------------------------------------------------------------------------------------------- Shapiro-Wilk testi scipy.stats modülündeki shapiro fonksiyonuyla yapılmaktadır. Fonksiyonun parametrik yapısı şöyledir: 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) result_norm = shapiro(sample_norm) sample_uniform = uniform.rvs(100, 100, size=1000) result_uniform = shapiro(sample_uniform) 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() #---------------------------------------------------------------------------------------------------------------------------- Pekiyi Kolmogorov-Simirnov testi ile Shapiro-Wilk testi arasında ne farklılık vardır? Aslında bu iki test arasında bazı spesifik farklılıklar bulunmaktadır. Ancak bunların her ikisi de normalliğin test edilmesi için kullanılabilmektedir. Shapiro-Wilk testinin kullanımı daha kolaydır. Ayrıca anakütleden çekilen örnekler küçükse (tipik olarak <= 50) Shapiro-Wilk testi Kolmogorov-Simirnov testine göre daha iyi bir sonucun elde edilmesine yol açmaktadır. Yani örneğiniz küçükse Shapiro-Wilk testini tercih edebilirsiniz. #---------------------------------------------------------------------------------------------------------------------------- #---------------------------------------------------------------------------------------------------------------------------- İstatistikte örneğe dayalı olarak anakütlenin ortalamasını (ve/veya standart sapmasını) tahmin 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. 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 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ına "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}]') #---------------------------------------------------------------------------------------------------------------------------- Anakütlenin standart sapmasının bilinmediği durumda yine eğer örnek yeteri büyük değilse (tipik olarak < 30 biçimindeyse) anakü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) anakütlenin hesaplamalarda normal dağılmış olması gerekmemektedir. Tabii t dağılımın asıl kullanılma nedeni örneğin küçük olduğu (tipik olarak < 30) durumlardır. Zaten örnek büyüdükçe (tipik olarak >= 30) t dağılımı normal çok yaklaşmaktadır. Pekiyi örneğimiz küçükse (tipik olarak < 30) ve anakü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 verilerin 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. 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. Eğer veriler veritabanlarının içerisindeyse uygulamacının ilgili veri tabanı yötetim sistemine bağlanıp verileri oradan ç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. İ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. #---------------------------------------------------------------------------------------------------------------------------- #---------------------------------------------------------------------------------------------------------------------------- Veriler toplandıktan ya da elde edildiktan sonra hemen işleme sokulamayabilir. Veriler üzerinde çeşitli ön işlemlerin yapılması gerekebilmektedir. Bu ön işlemlere "verilerin kullanıma hazır hale getirilmesi (data preparation)" denilmektedir. Biz bu bölümde verilerin kullanıma 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 tipik olarak aşağıdaki süreçlerden geçilerek kullanıma hazır hale getirilmektedir: 1) Verilerin Temizlenmesi (Data Cleaning): Veriler eksik, geçersiz ya da aşırı uç değerler (outliers) içerebilir. Aşırı uç değerler genellikle "şüpheli" değerler olarak ele alınmaktadırç. Verilerden eksiki, geçersiz ve aşırı uç değerlerin atılmasına verilerin temizlenmesi denilmektedir. 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 seçilmesi stkinliğine "özellik seçimi" denilmektedir. Örneğin bir veri tablosundaki kişinin "Adı Soyadı" sütunu, ya da verinin sıra numarasını belirten "Indeks" 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. 3) Verilerin Dönüştürülmesi (Data Transformation): Kategorik veriler, tarih ve zaman belirten veriler, resimsel ya da yazısal 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 yönde etkileme eğilimindedi. Sütunların skalalarını birbirine benzer hale getirme sürecine "özellik ölçeklemesi (feature scaling)" denilmektedir. 4) Özellik Mühendisliği (Feature Engineering): Veri kümesindeki var olan sütunlardan olmayan başka sütunların oluşturulması sürecine "özellik mühendisliği" denilmektedir. Yani özellik mühendisliği var olan bilgilerden hareketle önemli başka bilgilerin elde edilmesi sürecidir. Örneğinin kişinin boy ve kilosu biliniyorsa biz vücut kitle endeksini veri kümesine yeni bir sütun larak ekleyebiliriz. 5) Boyutsal Özellik İndirgemesi (Dimensionality 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 verilerden (satırları kastediyoruz) hareketle yeni verilerin oluşturulması (yeni satırların oluşturulması) sürecine "verilerin çoğaltılması (data augmentation)" denilmektedir. Örneğin bir resim döndürülerek ondan pek çok resim elde edilebilir. Benzer biçimde örneğin bir resmin çeşitli kısımları kırpılarak 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 tablodaki diğer sütunlarla açıklanamamaktadır. Eğer eksik veriler "tamamen rastgele" ve "rastgele" oluşmuyorsa bu kategoride değerlendirilebilir. Örneğin bir sağlık çalışmasında, kontrol randevusundaki değerler veri kümesinin bir sütununu oluşturuyor olsun. Hastalar tedaviden memnun kalmadıkları için takip randevularına gitmiyorsa bu durumda eksik veriler diğer sütunlarla ilgili değildir. Yine bu türden eksik verilerin bulunduğu satırlar veri kümesinden atılırsa veri kümesinde bir yanlılık oluşabilecektir. #---------------------------------------------------------------------------------------------------------------------------- #---------------------------------------------------------------------------------------------------------------------------- Eksik veriler CSV dosyalarında genellikle "boş bir eleman biçiminde ya da NaN" , "nan" "NULL", "null" biçiminde ya da "NA" biçiminde karşımıza çıkmaktadır. Eksik veri içeren CSV dosyaları Pandas kütüphanesinin read_csv fonksiyonu ile okunduğunda bunlara karşı gelen DataFrame elemanları NaN (Not a Number) olarak elde edilmektedir. (NaN değerinin IEEE 754 kayan noktalı formatlarda geçerli bir sayı belirtmediğini anımsayınız.) Ö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', '', '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 i simli 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 veri 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ütunlarda 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 bu amaçla kullamı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 başka değerlerle doldurulması işlemine İngilizce "imputation" denilmektedir. Eğer eksik verilerin bulunduğu satırın (nadiren de sütunun) atılması uygun görülmüyorsa "imputation" uygulanmalıdır. #---------------------------------------------------------------------------------------------------------------------------- #---------------------------------------------------------------------------------------------------------------------------- Pandas'ta 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. isna ve isnull aynı fonksiyonu belirtmektedir. 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ütunsal 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(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 elde 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 parameresinin default değeri 0'dır. Yani default durumda satırlar atılmaktadır. Ancak axis=1 argümanıyla sütunların da atılmasını sağlayabiliriz. Metot 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. Veri kümesinde çok fazla eksik veri içeren satır bulunuyorsa eksik verilerin bulunduğu satırların atılması önemli bir veri kaybına yol açabilmektedir. Eksik verilerin bulunduğu satırların atılması bazen veri kümesini yanlı hale de getirebilir. (Örneğin veri kümesi anket yöntemiyle elde ediliyor olsun. Kadınların yaşlarını söylememesi Türk kültüründe yaygın bir davranıştır. Böyle bir veri kümesinde eksik verilerin bulunduğu satırlar atılırsa geri kalan satırların çoğu erkeklere ilişkin olabilir.) İşte bu tür durumlarda eksik verilerin bulunduğu satırları atmak yerine eksik değerleri başka değerlerle doldurmaya çalışmak (imputation) daha uygun olabilmektedir. Aşağıda "Melbourne Housing Snapshot (MHS)" veri kümesinde eksik verilerin bulunduğu satır ve sütunların atılmasına bir örnek verilmiştir. #---------------------------------------------------------------------------------------------------------------------------- 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 İngilizce "imputation" denildiğini söylemiştik. Biz İngilizce "imputation" sözüğü için Türkçe "doldurma" sözcüğünü de kullanacağız. Kullanılan tipik imputation stratejileri şunlardır:: - Sütun sayısal ise Eksik verileri sütun ortalaması ile doldurulması - Sütun kaegorik ya da sırasal ise eksik verilerin mod değeri ile doldurması - Sütunlarda uç değeler varsa eksik değerlerin 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 biçimindeki veri kğmelerinde önceki ya da sonraki sütun değerleriyle doldurulması - Eksik değerlerin regresyonla tahmin tahmin edilerek doldurulması - Eksik değerlerin k-NN (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. #---------------------------------------------------------------------------------------------------------------------------- #---------------------------------------------------------------------------------------------------------------------------- Ş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. Imputation işlemi için bu sü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ı kullanılabilir. 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. Eksik değerleri ortalamanın yuvarlanmış haliyle doldurabiliriz: 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 CouncilArea 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 nesnesinin ilk elemanını alarak mod 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 imar mevzuatları 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 uygulamalarında en çok tercih edilen kütüphanelerden biri "scikit-learn" isimli kütüphanedir. NumPy ve Pandas sayısal işlemler için kullanılan genel amaçlı ve temel kütüphanelerdir. SciPy ise matematik ve lineer cebir konularına odaklanmış bir sayısal analiz kütüphanesidir. Oysa scikit-learn makine öğrenmesi amacıyla tasarlanmış ve bu amaçla kullanılan bir kütüphanedir. scikit-learn kütüphanesinin temelleri 2007 yılında atıldı. Önceleri SciPy kütüphanesine bir eklenti oluşturma amacıyla geliştirilmeye başlandı. İlk sürümü 2010 yılında yayınlandı (0.1 sürümü). 2012'dne sonra hızla popülerleşmeye başladı. 2015 ve sonrasında pek çok özelliği bünyesine katarak ve sürekli geliştirilerek bugünkü durumuna geldi. 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. scikit-learn içerisindeki fonksiyonlar ve sınıflar matematiksel ve istatistiksel ağırlıklı öğrenme yöntemlerini uygulamaktadır. scikit-learn kütüphanesinin import ismi sklearn biçimindedir. Örneğin: import sklearn Ancak genellikle uygulamacılar kütüphaneyi bir bütün olarak import etmek yerine kullanacakları öğeleri from import deyimi ile import etmeyi tercih ederler. Örneğin: from sklearn.impute import SimpleImputer #---------------------------------------------------------------------------------------------------------------------------- #---------------------------------------------------------------------------------------------------------------------------- scikit-learn 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. (Burada “eğitme” demekle denetimli bir öğrenmeyi kastetmiyoruz. Bunu genel bir terim olarak kullanıyoruz.) 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 özniteliklerinde 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 sınıfların transform metotlarıyla yapılmaktadır. Bir veri kümesi üzerinde fit işlemi uyguladıktan sonra burada oluşan bilgilerle birden fazla veri kümesini transform edebiliriz. Örneğin: si.fit(dataset) result1 = si.transform(dataset_foo) result2 = si.transform(dataset_bar) ... fit ve transform metotları bizden bilgiyi NumPy dizisi olarak, Pandas'ın Series ya da DataFrame nesnesi olarak ya da Python listesi olarak alabilmektedir. fit metotları nesnenin kendisine, transform metotları da transform edilmiş NumPy dizilerine geri dönmektedir. 4) Eğer fit edilecek veri kümesi ile transform edilecek veri kümesi aynı ise bu durumda önce fit sonra transform işlemini ayrı ayrı yapmak 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) Bazı sınıfların 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. fit, transform ve fit_transform metotları iki boyutlu bir veri kümelerini kabul etmektedir. Bu nedenle örneğin biz bu metotlara Pandas'ın DataFrame nesnelerini verebiliriz, Series nesnelerini veremeyiz. #---------------------------------------------------------------------------------------------------------------------------- #---------------------------------------------------------------------------------------------------------------------------- 20. Ders - 03/03/2024 - Pazar #---------------------------------------------------------------------------------------------------------------------------- #---------------------------------------------------------------------------------------------------------------------------- Scikit-learn sınıflarının genel kullanımını açıkladıktan sonra şimdi eksik verilerin doldurulması işleminde kolaylık sağlayan scikit-learn SimpleImputer sınıfını görebiliriz. SimpleImputer sınıfının kullanılması yukarıda açıkladığımız genel kalıba uygundur. Önce nesne yaratılır, sonra fit ve transform işlemleri yapılır. Sınıfın __init__ metodunun parametrik yapısı şöyledir: class sklearn.impute.SimpleImputer(*, missing_values=nan, strategy='mean', fill_value=None, copy=True, add_indicator=False, keep_empty_features=False) Görüldüğü gibi metodun bütün parametreleri default değer almış durumdadır. strategy parametresi doldurmanın nasıl yapılacağını belirtmektedir. Bu parametrenin default değerinin "mean" biçiminde olduğunu görüyorsunuz. Yani default durumda eksik veriler sütun ortalamaları ile doldurulmaktadır. "mean" dışında şu stratejiler bulunmaktadır: "median" "most_frequent" "constant" "constant" stratejisi doldurmanın belli bir değerle yapılacağı anlamına gelmektedir. 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 başvurabilirsiniz. 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): 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ından 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') #---------------------------------------------------------------------------------------------------------------------------- #---------------------------------------------------------------------------------------------------------------------------- Aslında makine öğrenmesi algoritmalarının bazılarında kategorik verilerin 0'dan itibaren LabelEncoder ya da OrdinalEncoder sınıfı ile sayısallaştırılması olumsuz etkilere yol açabilmektedir. Çünkü bu durumda bazı algoritmalar bu kategorik verileri sanki "sıralı (ordinal)" bir veriymiş gibi ele almaktadır. Örneğin veri kümesinin bir sütununda "RenkTercihi" olsun. Bu RenkTercihi de "Kırmızı", "Mavi" ve Yeşil renklerindne oluşuyor olsun. Biz bu sütunu LabelEncoder ile sayısal hale getirdiğimizde örneğin Kırmızı = 0, Mavi = 1, Yeşil = 2 biçiminde renklere numara verilecektir. İşte burada makine öğrenmesi algoritmaları sanki "Mavi" > "Kırmızı", "Yeşil > "Mavi" gibi bir durum varmış gibi sonuca yol açabilecektir. Hatta örneğin bu kodlamadan "Kırmızı" + "Mavi" = "Mavi" gibi gerçekte var olmayan özellikler de ortaya çıkacaktır. İşte bu nedenle kategorik olguların birden fazla sütunla ifade edilmesi yoluna gidilmektedir. Kategorik verilerin birden fazla sütunla ifade edilmesinde en yaygın kullanılan yöntem "one hot encoding" denilen yöntemdir. Bu yöntemde sütundaki kategorilerin sayısı hesaplanır. Veri tablosuna bu sayıda sütun eklenir. "One hot" terimi "bir grup bitten hepsinin sıfır yalnızca bir tanesinin 1 olma durumunu" anlatmaktadır. İşte bu biçimde n tane kategori n tane sütunla ifade edilir. Her kategori yalnızca tek bir sütunda 1 diğer sütunlarda 0 olacak biçimde kodlanır. Örneğin: RenkTercihi ----------- Kırmızı Kırmızı Mavi Kırmızı Yeşil Mavi Yeşil ... Burada 3 renk olduğunu düşünelim. Bunun "one-hot-encoding" dönüştürmesi şöyle olacaktır: Kırmızı Mavi Yeşil 1 0 0 1 0 0 0 1 0 1 0 0 0 0 1 0 1 0 0 0 1 ... Eğer sütundaki kategori sayısı 2 tane ise böyle sütunlar üzerinde "one-hot-encoding" uygulamanın bir faydası yoktur. Bu tür ikili sütunlar 0 ve 1 biçiminde kodlanabilir. (Yani bu işlem LabelEncoder sınıfyla yapılabilir). Biz de kursumuzda yalnızca iki kategori içeren sütunları "one-hot-encoding" ile kodlamayacağız. #---------------------------------------------------------------------------------------------------------------------------- #---------------------------------------------------------------------------------------------------------------------------- "one-hot-encoding" yapmanın çok çeşitli yöntemleri vardır. Örneğin scitkit-learn içerisindeki preprocessing modülünde bulunan OneHotEncoder sınıfı bu işlem için kullanılabilir. Sınıfın genel kullanımı diğer sckit-learn sınıflarında olduğu gibidir. Yani önce fit işllemi sonra transform işlemi yapılır. fit işleminden sonra sütunlardaki "tek olan (unique)" elemanlar sınıfın categories_ örnek özniteliğine NumPy dizilerinden oluşan bir liste biçiminde kaydedilmektedir. Örneğimizde kullanacağımız test.csv" dosyası şöyle olsun: AdıSoyadı,Kilo,Boy,Yaş,RenkTercihi Sacit Bulut,78,172,34,Kırmızı Ayşe Er,67,168,45,Yeşil Ahmet San,85,182,32,Kırmızı Macit Şen,98,192,65,Mavi Talat Demir,85,181,49,Yeşil Sibel Ünlü,72,172,34,Mavi Ali Serçe,75,165,21,Yeşil "one-hot-encoding" yapılırken DataFrame içerisine eski sütunu silip yeni sütunlar eklenmelidir. DataFrame sütununa bir isim vererek atama yapılırsa nesne o sütunu zaten otomatik eklemektedir. "one-hot-encoding" ile oluşturulan sütunların isimleri önemli değildir. Ancak OneHotEncoder nesnesi önce sütunu np.unique fonksiyonuna sokmakta ondan sonra o sırada encoding işlemi yapmaktadır. NumPy'ın unique fonksiyonu aynı zamanda sıraya dizme işlemini de zaten yapmaktadır. Dolayısıyla OneHotEncoder aslında kategorik değerleri alfabetik sıraya göre sütunsal olarak dönüştürmektedir. Yukarıda da belirttiğimiz gibi OneHotEncoder nesnesi fit işlemi yapıldığında zaten kategorileri categories_ isimli örnek özniteliği yoluyla bir NumPy dizi listesi olarak bize vermektedir. Biz DataFrame eklemesi yaparken doğrudan bu isimleri kullanabiliriz. categories_ örnek özniteliğinin bir liste olduğuna dikkat ediniz. Çünkü birden fazla sütun tek hamlede "one-hot-encoding" yapılabilmektedir. Dolayısıyla programcı bu listenin ilgili elemanından kategorileri elde etmelidir. Yukarıda da belirttiğimiz gibi OneHotEncoder sınıfının fit ve transform metotları çok boyutlu dizileri kabul etmektedir. Bu durumda biz bu metotlara Pandas'ın Series nesnesini değil DataFrame nesnesini vermeliyiz. OneHotEncoder nesnesini yaratırken "sparse_output" parametresini False biçimde vermeyi unutmayınız. (Bu parametrenin eski ismi yalnızca "sparse" biçimindeydi). Çünkü bu sınıf default olarak transform edilmiş nesneyi "seyrek matris (sparse matrix)" olarak vermektedir. Elemanlarının büyük çoğunluğu 0 olan matrislere "seyrek matris (sparse matrix)" denilmektedir. Bu tür matrisler "fazla yer kaplamasın diye" sıkıştırılmış bir biçimde ifade edilebilmektedir. Seyrek matrisler üzerinde işlemler kursumuzun sonraki bölümlerinde ele alınacaktır. Yine OneHotEncoder nesnesi yaratılırken parametre olarak dtype türünü belirtebiliriz. Default dtype türü np.float64 alınmaktadır. Matris seyrek formda elde edilmeyecekse bu dtype türünü "uint8" gibi en küçük türde tutabilirsiniz. max_categories parametresi kategori sayısını belli bir değerde tutmak için kullanılmaktadır. Bu durumda diğer tüm kategoriler başka bir kategori oluşturmaktadır. Örneğin: df = pd.read_csv('test.csv') ohe = OneHotEncoder(sparse_output=False, dtype='uint8') transformed_data = ohe.fit_transform(df[['RenkTercihi']]) df.drop(['RenkTercihi'], axis=1, inplace=True) df[ohe.categories_[0]] = transformed_data Burada önce veri kümesi okunmuş sonra OneHotEncoder sınıfıyla fit_transform işlemi yapılmıştır. Daha sonra ise DataFrame içerisindeki "RenkTercihi" sütunu silinip onun yerine categories_ örnek özniteliğindeki kategori isimleri nesneye eklenmiştir. #---------------------------------------------------------------------------------------------------------------------------- import pandas as pd from sklearn.preprocessing import OneHotEncoder df = pd.read_csv('test.csv') ohe = OneHotEncoder(sparse_output=False, dtype='uint8') transformed_data = ohe.fit_transform(df[['RenkTercihi']]) df.drop(['RenkTercihi'], axis=1, inplace=True) df[ohe.categories_[0]] = transformed_data print(df) #---------------------------------------------------------------------------------------------------------------------------- DataFrame nesnesine yukarıdaki gibi birden fazla sütun eklerken dikkat etmek gerekir. Çünkü tesadüfen bu kategori isimlerine ilişkin sütunlardan biri zaten varsa o sütun yok edilip yerine bu kategori sütunu oluşturulacaktır. Bunu engellemek için oluşturacağınız kategori sütunlarını önek vererek isimlendirebilirsiniz. Önek verirken orijinal sütun ismini kullanırsanız bu durumda çakışma olmayacağı garanti edilebilir. Yani örneğin RenkTercihi sütunu için "Kırmızı", "Mavi" "Yeşil" isimleri yerine "RenkTercihi_Kırmızı", "RenkTercihi_Mavi" ve "RenkTercihi_Yeşil" isimlerini kullanabilirsiniz. Bu biçimde isim elde etmek "liste içlemiyle" oldukça kolaydır. Örneğin: category_names = ['RenkTercihi_' + category for category in ohe.categories_[0]] Aşağıda buna örnek verilmiştir. #---------------------------------------------------------------------------------------------------------------------------- import pandas as pd from sklearn.preprocessing import OneHotEncoder df = pd.read_csv('test.csv') ohe = OneHotEncoder(sparse_output=False, dtype='uint8') transformed_data = ohe.fit_transform(df[['RenkTercihi']]) df.drop(['RenkTercihi'], axis=1, inplace=True) category_names = ['RenkTercihi_' + category for category in ohe.categories_[0]] df[category_names] = transformed_data print(df) #---------------------------------------------------------------------------------------------------------------------------- Veri kümesinde birden fazla sütunda kategorik bilgiler olabilir. Bunlar OneHotEncoder sınıfıyla tek tek kodlanabilirler. Ancak aslında yukarıda da belirttiğimiz gibi OneHotEncoder sınıfı bir grup sütunu tek hamlede de kodlayabilmektedir. Örneğin "test.csv" veri kümemiz şöyle olsun: AdıSoyadı,Kilo,Boy,Yaş,RenkTercihi,Meslek Sacit Bulut,78,172,34,Kırmızı,Mühendis Ayşe Er,67,168,45,Yeşil,Mühendis Ahmet San,85,182,32,Kırmızı,Avukat Macit Şen,98,192,65,Mavi,Doktor Talat Demir,85,181,49,Yeşil,Avukat Sibel Ünlü,72,172,34,Mavi,Doktor Ali Serçe,75,165,21,Yeşil,Mühendis Burada "RenkTercihi"nin yanı sıra "Meslek" de kategorik bir sütundur. Bunun her ikisini birden tek hamlede "one-hot-encoding" işlemine sokabiliriz: ohe = OneHotEncoder(sparse_output=False, dtype='uint8') transformed_data = ohe.fit_transform(df[['RenkTercihi', 'Meslek']]) df.drop(['RenkTercihi', 'Meslek'], axis=1, inplace=True) categories1 = ['RenkTercihi_' + category for category in ohe.categories_[0]] categories2 = ['Meslek_' + category for category in ohe.categories_[1]] df[categories1 + categories2] = transformed_data #---------------------------------------------------------------------------------------------------------------------------- import pandas as pd from sklearn.preprocessing import OneHotEncoder df = pd.read_csv('test.csv') ohe = OneHotEncoder(sparse_output=False, dtype='uint8') transformed_data = ohe.fit_transform(df[['RenkTercihi', 'Meslek']]) df.drop(['RenkTercihi', 'Meslek'], axis=1, inplace=True) categories1 = ['RenkTercihi_' + category for category in ohe.categories_[0]] categories2 = ['Meslek_' + category for category in ohe.categories_[1]] df[categories1 + categories2] = transformed_data print(df) #---------------------------------------------------------------------------------------------------------------------------- Aslında makine öğrenmesi uygulamalarında veri kümesinin sütun isimlerinin hiçbir önemi yoktur. Dolayısıyla aslında biz "one-hot-encoding" işleminden önce NumPy dizisine geçip hiç isimlerini oluşturmadan geri kalan kısmı NumPy üzerinde de yapabiliriz. Aşağıda buna bir örnek verilmektedir. #---------------------------------------------------------------------------------------------------------------------------- import numpy as np import pandas as pd from sklearn.preprocessing import OneHotEncoder df = pd.read_csv('test.csv') ohe = OneHotEncoder(sparse_output=False, dtype='uint8') transformed_data = ohe.fit_transform(df[['RenkTercihi', 'Meslek']]) df.drop(['AdıSoyadı', 'RenkTercihi', 'Meslek'], axis=1, inplace=True) dataset = df.to_numpy() dataset = np.concatenate((dataset, transformed_data), axis=1) print(dataset) #---------------------------------------------------------------------------------------------------------------------------- one-hot-encoding 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. #---------------------------------------------------------------------------------------------------------------------------- #---------------------------------------------------------------------------------------------------------------------------- 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 insanları 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 yöntemler grubudur. Her ne kadar artık yapay sinir ağlarının insanın sinir sistemiyle bir ilgisi kalmadıysa da biz yine de klasik biçimde önce bu yöntemler grubunun ilham kaynağı olan insanın sinir sistemi üzerinde bazı açıklamalarla konuya başlayacağız. 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 "dendrit (dendrite)" denilen bir kısmı vardır. Pek çok nöronda bir dal biçiminde uzanan aksonlar da bulunmaktadır. Aksonların uçlarında küçük "düğmecikler (terminal buttons)" vardır. Bir nöron ateşlendiğinde bu düğmeciklerden "nörotransmiter (neurotransmitter)" denilen kimyasallar zerk edilir. Bunlar diğer nöronun reseptörleri tarafından alınmaktadır. Ateşleme nörondaki "aksiyon potansiyeli (action potantial)" belli bir düzeye geldiğinde gerçekleşmektedir. Bir nöron duruma göre yüzlerce nörona bağlı olabilmektedir. Bir nöronun akson ucu ile diğer nönronun dendrit 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 ise "antagonist" denilmektedir. Agonist etki çeşitli biçimlerde sağlanabilmektedir. Örneğin bunun için presinaptik nöronda (nörotransmiterleri zerk eden nöron) nörotransmiter miktarı ya da örneğin 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 onu kimyasal düzeye dönüştürmekte 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şlenir. 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 elimizi hareket ettirmek istediğimizde bu süreç beynin emir vermesiyle başlayan nöral iletinin ele ulaşıp oradaki kasları hareket ettirmesiyle sağlanmaktadır. Dolayısıyla bu nöral ileti bozulursa felç durumu ortaya çıkmaktadır. Nöronlarda "miyelin kılıfı (myline sheet)" denilen özel bir kılıf bulunabilmektedir. Bu kılıf nöral iletiyi hızlandırmaktadır. Beyinde bu kılıfın olduğu nöronlar beyaz renkte gözüktüğü için bu bölgelere "beyaz madde (white matter)", miyelinsiz nöronlar da gri bir biçimde gözüktüğü için bu nöronların bulunduğu bölgelere de "gri madde (gray matter)" denilmektedir. Makine öğrenmesindeki yapay sinir ağları yukarıda da belirttiğimiz gibi insanın sinir sisteminden ilham alınarak tasarlanmıştır. Ancak artık bu ilham noktası önemini kaybetmiş gibidir. Bu nedenle pek çok uzman artık bu konuyu "yapay sinir ağları" yerine yalnızca "sinir ağları" biçiminde ifade etmektedir. #---------------------------------------------------------------------------------------------------------------------------- #---------------------------------------------------------------------------------------------------------------------------- Yapay sinir ağları yapay nöronların birbirlerine bağlanmasıyla oluşturulmaktadır. Bir nöronun çeşitli girdileri olabilir fakat yalnızca bir tane çıktısı vardır. Nöronun girdileri aslında veri kümesindeki satırları temsil etmektedir. Yani veri kümesindeki satırlar nöronun girdileri olarak kullanılmaktadır. Nöronun girdilerini xi ile temsil edersek her girdi "ağırlık (weight) değeri" denilen bir değerle çarpılır ve bu çarpımların toplamları elde edilir. Ağırlık değerlerini wi ile gösterirsek bu xi değerleri onlara karşı gelen wi değerleriyle çarpılıp toplanmaktadır. Örneğin nöronun 5 tane girdisi olsun, bu 5 girdi aşağıdaki gibi 5 ayrı ağı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 değeri "bias" denilen bir değerle toplanır. Biz bias dieğerini b ile temsil edeceğiz. Bu durumda nöronda elde edilen toplam şöyle olacaktır: total = x1w1 + x2w2 + x3w3 + x4w4 + x5w5 + b İşte elde edilen 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. Bu işlemleri aşağıdaki gibi ifade edebiliriz. out = activation(x1w1 + x2w2 + x3w3 + x4w4 + x5w5 + b) Dot proıduct işlemini matrisel olarak XW biçiminde ifade edersek yukarıdaki yukarıdaki eşitliği daha sade biçimde aşağıdaki gibi de yazabiliriz: out = activation(XW + b) Bir nöronun çıktısı başka nöronlara girdi yapılabilmektedir. Böylece nöronlar birbirine bağlanarak tüm ağdan nihai bir çıktı elde edilir. #---------------------------------------------------------------------------------------------------------------------------- #---------------------------------------------------------------------------------------------------------------------------- Şimdi ağımızda tek bir nöron değil K tane farklı nöronun bulunduğunu düşünelim. N tane girdinin de (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 çıktı değeri elde edilecektir. Buradaki XW işleminin artık dot product belirtmediğine bir matris çarpımı belirttiğine dikkat ediniz. Gösterimimizdeki X matrisi bir satır vektörü durumundadır: X = [x1, x2, x3, ..., xn] Bu matris Nx1 boyutundadır. W matrisi ise aşağıdaki görünümdedir: w11 w21 w31 ... wk1 w12 w22 w31 ... wk2 w13 w23 w33 ... wk3 ... ... ... ... ... w1n w2n w3n ... wnk Bu matris de NxK boyutundadır. 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] Bu matrisin de 1XK boyutunda olduğuna dikkat ediniz. Böylece XW + b işleminden 1XK boyutunda bir matris elde edilecektir. Tabii biz bu matris çarpımını WX + b biçiminde ters de oluşturabilirdik. Bu durumda W matrisi şöyle olacaktır: w11 w12 w13 ... w1n w21 w22 w23 ... w2n w31 w32 w33 ... w3n ... ... ... ... ... wk1 wk2 wk3 ... wkn Söz konusu X matrisi de şöyle olacaktır: x1 x2 x3 ... xk Tabii bu durumda b matrisi de şöyle olacaktır: b1 b2 b3 ... bk Artık burada W matrisinin artık KxN boyutunda olduğuna, X ve b matrislerinin de Kx1 boyutunda olduğuna dikkat ediniz. Genellikle XW + b gösterimi yerine WX + b gösterimi tercih edilmektedir. #---------------------------------------------------------------------------------------------------------------------------- #---------------------------------------------------------------------------------------------------------------------------- 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. Pekiyi bu değerleri nasıl elde edebiliriz? 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. Bizim çeşitli dairelerin bu bilgilerini elde etmiş olmamız ve onların satış fiyatlarını da biliyor olmamız gerekir. Yani bizim işin başında sinir ağına girdi yapacağımız bilgilerle ağın vermesi gereken gerçek çıktılardan oluşan bir veri kümesine gereksinimimiz vardır. İşte ağın eğitimi için bu veri kümesini kullanırız. Ağımızı bu gerçek verilerle eğittikten sonra artık nöronlardaki w ve b değerleri konumlandırılmış olacaktır. Biz de kestirim yapmak istediğimizde ağımıza girdi olarak yukarıdaki bilgilere sahip bir dairenin özelliklerini veririz. Ağımız da bize çıktı olarak o dairenin olması gereken fiyatını verir. #---------------------------------------------------------------------------------------------------------------------------- #---------------------------------------------------------------------------------------------------------------------------- Biz yukarıda bir sinir ağının temel çalışma mekanizmasını açı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 olmalıdır? - Katmalardaki nöronların sayıları ve katman bağlantıları nasıl 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üm ü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 zaten kendiliğinden "lojistik olmayan regresyon" işlemleri anlaşılmaktadır. Bu tür regresyonlarda girdilerden hareketle kategorik bir değer değil sayısal bir değer 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 ise çı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. Yukarıda da belirttiğimiz gibi 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 makine öğrenmesi bağlamında "sınıflandırma (classification)" terimini kullanacağız. Kursumuzda kategorik olmayan, sayısal çıktı veren regresyonlar için ise genellikle "regresyon" terimini bazen de vurgulamak amacıyla "lojistik olmayan regresyon" terimini kullanacağız. Lojistik regresyondaki "lojistik" sözcüğünün günlük hayatta çokça karşılaştığımız "lojistik hizmetlerdeki" "lojistik" sözcüğü 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 "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. Buradaki sınıflandırma işlemi ikili sınıflandırma işlemidir. 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 bir ö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. Yukarıda da belirttiğimiz gibi istatistikte "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 grubu için kullanılan bir terimdir. Yapya sinir ağlarında 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ı ise 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 veri kümesine ilişkin 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ının 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 (yani girdi katmanının çıktıları) 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ı aynı olmak zorunda da 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 genel olarak çıktı katmanları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 kestirim başarısının düşmesine de yol açabilmektedir. Yani gerekmediği halde ağa saklı katman eklemek, katmanlardaki nöron sayılarını artırmak bir fayda sağlamamakta tersine kestirim başarısını düşürebilmektedir. Ancak görüntü tanıma gibi yazı anlamlandırma 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. Doğrusal olarak ayrıştırılabilirlik kursumuzda başka bir bölümde ele alınmaktadır. - Tek saklı katmanlı modeller aslında pek çok sınıflandırma problemini ve regresyon problemini 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 sinir ağlarına 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. Ağı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. Ağı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ı ağın ürettiği değerlerle gerçek değerler arasındaki farklılığı temsil eden fonksiyonlardır. Loss fonksiyonları genel olarak iki girdi alıp bir çıktı vermektedir. Loss fonksiyonunun girdileri gerçek değerler ile ağın ürettiği değerlerdir. Çıktı değeri ise aradaki farklığı belirten bir değerdir. Eğitim sırasında gitgide loss fonksiyonun değerinin düşmesini bekleriz. Tabii loss değerinin düşmesi aslında ağın gerçek değerlere daha yakın değerler üretmesi anlamına gelmektedir. loss fonksiyonları çıktının biçimine yani problemin türüne bağlı olarak seçilmektedir. Örneğin ikili sınıflandırma problemleri için "binary cross-entropy", çoklu sınıflandırma problemleri için "categorical cross-entropy", regresyon problemleri için "mean squared error" isimli loss fonksiyonları tercih edilmektedir. Optimizasyon algoritmaları aslında genel yapı olarak birbirlerine benzemektedir. Pek çok problemde bu algoritmaların çoğu benzer performans göstermektedir. En çok kullanılan optimizasyon algoritmaları "rmsprop", "adam" ve "sgd" algoritmalarıdır. Bu algoritmalar "gradient descent" denilen genel optimizasyon yöntemini farklı biçimlerde uygulamaktadır. Optimizasyon algoritmaları kursumuzun ilerleyen bölümlerinde ele alınacaktır. compile metodunda optimizasyon algoritması bir yazı olarak ya da tensorflow.keras.optimizers modülündeki sınıflar türünden bir sınıf nesnesi olarak girilebilmektedir. Örneğin: model.compile(optimizer='rmsprop', ...) Örneğin: from tensorflow.keras.optimizers import RMSprop rmsprop = RMSprop() model.compile(optimizer=rmsprop, ...) Tabii optimizer parametresinin bir sınıf nesnesi olarak girilmesi daha detaylı belirlemelerin yapılmasına olanak sağlamaktadır. Optimizasyon işlemlerinde bazı parametrik değerler vardır. Bunlara makine öğrenmesinde "üst düzey parametreler (hyper parameters)" denilmektedir. İşte optimizasyon algoritması bir sınıf nesnesi biçiminde verilirse bu sınıfın __init__ metodunda biz bu üst düzey parametreleri istediğimiz gibi belirleyebiliriz. Eğer optimizasyon algoritması yazısal biçimde verilirse bu üst düzey parametreler default değerlerle kullanılmaktadır. optimzer parametresinin default değeri "rmsprop" biçimindedir. Yani biz bu parametre için değer girmezsek default optimazyon algoritması "rmsprop" olarak alınacaktır. loss fonksiyonu compile metoduna yine isimsel olarak ya da tensorflow.keras.losses modülündeki sınıflar türünden sınıf nesleri biçiminde ya da doğrudan fonksiyon olarak girilebilmektedir. loss fonksiyonları kısa ya da uzun isim olarak yazısal biçimde kullanılabilmektedir. Tipik loss fonksiyon isimleri şunlardır: 'mean_squared_error' ya da 'mse' 'mean_absolute_error' ya da 'mae' 'mean_absolute_percentage_error' ya da 'mape' 'mean_squared_logarithmic_error' ya da 'msle' 'categorical_crossentropy' 'binary_crossentropy' tensorflow.keras.losses modülündeki fonksiyonlar da kısa ve uzun isimli olarak bulunabilmektedir: tensorflow.keras.losses.mean_squared_error ya da tensorflow.keras.losses.mse tensorflow.keras.losses.mean_absolute_error ya da tensorflow.keras.losses.mae tensorflow.keras.losses.mean_absolute_percentage_error ya da tensorflow.keras.losses.mape tensorflow.keras.losses.mean_squared_logarithmic_error ya da tensorflow.keras.losses.msle tensorflow.keras.losses.categorical_crossentropy tensorflow.keras.losses.binary_crossentropy Bu durumda compile metodu örnek bir biçimde şöyle çağrılabilir: model.compile(optimizer='rmsprop', loss='binary_crossentropy') compile metodunun üçüncü önemli parametresi "metrics" isimli parametredir. metrics parametresi bir liste ya da demet olarak girilir. metrics parametresi daha sonra açıklanacak olan "sınama (validation)" işlemi için kullanılacak fonksiyonları belirtmektedir. Sınamada her zaman zaten bizim loss fonksiyonu olarak belirttiğimiz fonksiyon kullanılmaktadır. metrics parametresinde ilave fonksiyonlar da girilebilmektedir. Örneğin ikili sınıflandırma problemleri için tipik olarak "binary_accuracy" denilen metrik fonksiyon, çoklu sınıflandırma için "categorical_accuracy" denilen metrik fonksiyon ve regresyon problemleri için de "mean_absolute_error" isimli metrik fonksiyon sıklıkla kullanılmaktadır. Örneğin ikili sınıflandırma problemi için biz eğitim sırasında "loss" değerinin yanı sıra "binary_accuracy" değerini de elde etmek isteyelim. Bu durumda compile metodunu şöyle çağırmalıyız: model.compile(optimizer='rmsprop', loss='binary_crossentropy', metrics=['binary_accuracy']) 5) Model derlenip çeşitli belirlemeler yapıldıktan sonra artık gerçekten eğitim aşamasına geçilir. Eğitim süreci Sequential sınıfının fit metoduyle yapılmaktadır. fit metodunun en önemli parametresi ilk iki parametre olan x ve y veri kümeleridir. Biz burada training_dataset_x ve training_dataset_y verilerini fit metodunun ilk iki paramtresine geçirmeliyiz. fit metodunun önemli bir parametresi batch_size isimli parametredir. Eğitim işlemi aslında satır satır değil batch batch yapılmaktadır. batch bir grup satıra denilmektedir. Yani ağa bir grup satır girdi olarak verilir. Ağdan bir grup çıktı elde edilir. Bu bir grup çıktı ile bu çıktıların gerçek değerleri loss fonksiyonuna sokulur ve optimizasyon algoritması çalıştırılarak w ve bias değerleri güncellenir. Yani optimizasyon algoritması her batch işlemden sonra devreye sokulmaktadır. Batch büyüklüğü fit metodunda batch_size parametresiyle belirtilmektedir. Bu değer girilmezse batch_size 32 olarak alınmaktadır. 32 değeri pek çok uygulama için uygun bir değerdir. Optimizasyon işleminin satır satır yapılması yerine batch batch yapılmasının iki önemli nedeni vardır: Birincisi işlem miktarının azaltılması, dolayısıyla eğitim süresinin kısaltılmasıdır. İkincisi ise "overfitting" denilen olumsuz durum için bir önlem oluşturmasıdır. Overfitting hakkında ileride açıklama yapılacaktır. fit metodunun diğer önemli parametresi de "epochs" isimli parametredir. eğitim veri kümesinin eğitim sırasında yeniden eğitimde kullanılmasına "epoch" işlemi denilmektedir. Örneğin elimizde 1000 satırlık bir eğitim veri kümesi olsun. batch_size parametresinin de 20 olduğunu varsayalım. Bu durumda bu eğitim veri kümesi 1000 / 20 = 50 batch işleminde bitecektir. Yani model parametreleri 50 kere ayarlanacaktır. Pek çok durumda eğitim veri kümesinin bir kez işleme sokulması model parametrelerinin iyi bir biçimde konumlandırılması için yetersiz kalmaktadır. İşte eğitim veri kümesinin birden fazla kez yani fit metodundaki epochs sayısı kadar yeniden eğitimde kullanılması yoluna gidilmektedir. Pekiyi epochs değeri ne olmalıdır? Aslında bunu uygulamacı belirler. Az sayıda epoch model parametrelerini yeterince iyi konumlandıramayabilir. Çok fazla sayıda epoch "overfitting" denilen olumsuz duruma zemin hazırlayabilir. Ayrıca çok fazla epoch eğitim zamanını da uzatmaktadır. Uygulamacı epoch'lar sırasında modelin davranışına bakabilir ve uygun epoch sayısında işlemi kesebilir. Eğitim sırasında Keras bizim belirlediğimiz fonksiyonları çağırabilmektedir. Buna Keras'ın "callback" mekanizması denilmektedir. Uygulamacı bu yolla model belli bir duruma geldiğinde eğitim işlemini kesebilir. Ya da uygulamacı eğer eğitim çok uzamayacaksa yüksek bir epoch ile eğitimini yapabilir. İşlemler bitince epoch'lardaki performansa bakabilir. Olması gerekn epoch değerini kestirebilir. Sonra modeli yeniden bu sayıda epoch ile eğitir. fit metodunun shuffle parametresi her epoch'tan sonra eğitim veri kümesinin karıştırılıp karıştırılmayacağını belirtmektedir. Bu parametre default olarak True biçimdedir. Yani eğitim sırasında her epoch'ta eğitim veri kümesi karıştırılmaktadır. #---------------------------------------------------------------------------------------------------------------------------- #---------------------------------------------------------------------------------------------------------------------------- 27. Ders - 30/03/2024 - Cumartesi #---------------------------------------------------------------------------------------------------------------------------- #---------------------------------------------------------------------------------------------------------------------------- Modelin fit metodu ile eğitilmesi sırasında "sınama (validation)" denilen önemli bir kavram daha devreye girmektedir. Sınama işlemi test işlemine benzemektedir. Ancak test işlemi tüm model eğitildikten sonra yapılırken sınama işlemi her epoch'tan sonra modelin eğitim sürecinde yapılmaktadır. Başka bir deyişle sınama işlemi model eğitilirken yapılan test işlemidir. Epoch'lar sırasında modelin performansı hakkında bilgi edinebilmek için sınama işlemi yapılmaktadır. Sınamanın yapılması için fit metodunun validation_split parametresinin 0 ile 1 arasında oransal bir değer olarak girilmesi gerekir. Bu oransal değer eğitim veri kümesinin yüzde kaçının sınama için kullanılacağını belirtmektedir. Örneğin validation_split=0.2 eğitim veri kümesinin %20'sinin sınama için kullanılacağını belirtmektedir. fit metodu işin başında eğitim veri kümesini eğitimde kullanılacak kısım ile sınamada kullanılacak kısım biçiminde ikiye ayırmaktadır. Sonra her epoch'ta yalnızca eğitimde kullanılacak kümeyi karıştırmaktadır. Sınama işlemi aynı kümeyle her epoch sonrasında karıştırılmadan yapılmaktadır. fit metodunda ayrıca bir de validation_data isimli bir parametre vardır. Bu parametre sınama verilerini girmek için kullanılmaktadır. Bazen programcı sınama verilerinin eğitim veri kümesinden çekilip alınmasını istemez. Onu ayrıca fit metoduna vermek isteyebilir. Tabii validation_data parametresi girildiyse artık validation_split parametresinin bir anlamı yoktur. Bu parametre girilse bile artık fonksiyon tarafından dikkate alınmaz. Örneğin: model.fit(training_dataset_x, training_dataset_y, batch_size=32, epochs=100, validation_split=0.2) validation_split parametresinin default değerinin 0 olduğuna dikkat ediniz. validation_split değerinin 0 olması epoch'lar sonrasında sınama işleminin yapılmayacağı anlamına gelmektedir. Pekiyi modelin fit metodunda her epoch'tan sonra sınama işleminde hangi ölçümler ekrana yazdırılacaktır? İşte compile metodunda belirtilen ve ismine metrik fonksiyonlar denilen fonksiyonlar her epoch işlemi sonucunda ekrana yazdırılmaktadır. Her epoch sonucunda fit metodu şu değerleri yazdırmaktadır: - Epoch sonrasında elde edilen loss değerlerinin ortalaması - Epoch sonrasında eğitim veri kümesinin kendisi için elde edilen metrik değerlerin ortalaması - Epoch sonrasında sınama için kullanılan sınama verilerinden elde edilen loss değerlerinin ortalaması - Epoch sonrasında sınama için kullanılan sınama verilerinden elde edilen metrik değerlerin ortalaması Bir epoch işleminin batch batch yapıldığını anımsayınız. Budurumda epoch sonrasında fit tarafından ekrana yazdırılan değerler bu batch işlemlerden elde edilen ortalama değerlerdir. Yani çrneğin her batch işleminden bir loss değeri elde edilir. Sonra bu loss değerlerinin ortalaması hesap edilerek yazdırılır. Eğitim veri kümesindeki değerler ile sınama veri kümesinden elde edilen değerler birbirine karışmasın diye fit metodu sınama verilerinden elde edilen değerlerin başına "val_" öneki getirmektedir. Örneğin biz ikili sınıflandırma problemi üzerinde çalışıyor olalım ve metrik fonksion olarak "binary_accuracy" kullanmış olalım. fit metodu her epoch sonrasında şu değerleri ekrana yazdıracaktır: loss (eğitim veri kümesinden elde edilen ortalama loss değeri) binary_accuracy (eğitim veri kümesinden elde edilen ortalama metrik değer) val_loss (sınama veri kümesinden elde edilen ortalama loss değeri) val_binary_accuracy (sınama veri kümesinden elde edilen ortalama metrik değer) Tabii compile metodunda birden fazla metirk değer de belirtilmiş olabilir. Bu durumda fit tüm bu metrik değerlerin ortalamasını ekrana yazdıracaktır. fit tarafından ekrana yazdırılan örnek bir çıktı şöyle olabilir: .... Epoch 91/100 16/16 [==============================] - 0s 3ms/step - loss: 0.5536 - binary_accuracy: 0.7230 - val_loss: 0.5520 - val_binary_accuracy: 0.7480 Epoch 92/100 16/16 [==============================] - 0s 3ms/step - loss: 0.5392 - binary_accuracy: 0.7251 - val_loss: 0.5588 - val_binary_accuracy: 0.7805 Epoch 93/100 16/16 [==============================] - 0s 3ms/step - loss: 0.5539 - binary_accuracy: 0.7088 - val_loss: 0.5666 - val_binary_accuracy: 0.8049 ... Burada loss değeri epoch sonrasında eğitim verilerinden elde edilen ortalama loss değerini, val_loss değeri epoch sonrasında sınama verilerinden elde edilen ortalama loss değerini, binary_accuracy epoch sonrasında eğitim verilerinden elde edilen ortalama isabet yüzdesini ve val_binary_accuracy ise epoch sonrasında sınama verilerinden elde edilen ortalama isabet yüzdesini belirtmektedir. Eğitim sırasında eğitim veri kümesindeki başarının sınama veri kümesinde görülmemesi eğitimin kötü bir yöne gittiğine işaret etmektedir. Örneğin ikili sınıflandırma probleminde epoch sonucunda eğitim veri kümesindeki binary_accuracy değerinin %99 olduğunu ancak val_binary_accuracy değerinin %65 olduğunu düşünelim. Bunun anlamı ne olabilir? Bu durum aslında epoch'lar sırasında modelin bir şeyler öğrendiği ama bizim istediğimiz şeyleri öğrenemediği anlamına gelmektedir. Çünkü eğitim veri kümesini epoch'larla sürekli bir biçimde gözden geçiren model artık onu ezberlemiştir. Ancak o başarıyı eğitimden bağımsız bir veri kümesinde gösterememektedir. İşte bu olguya "overfitting" denilmektedir. Yanlış bir şeyin öğrenilmesi bir şeyin öğrenilememesi kadar kötü bir durumdur. Overfitting oluşumunun çeşitli nedenleri vardır. Ancak overfitting epoch'lar dolayısıyla oluşuyorsa epoch'ları uygun bir noktada kesmek gerekir. #---------------------------------------------------------------------------------------------------------------------------- #---------------------------------------------------------------------------------------------------------------------------- 6) fit işleminden sonra artık model eğitilmiştir. Onun test veri kümesiyle test edilmesi gerekir. Bu işlem Sequential sınıfının evaluate isimli metodu ile yapılmaktadır. evaluate metodunun ilk iki parametresi test_dataset_x ve test_dataset_y değerlerini almaktadır. Diğer bir parametresi yine batch_size parametresidir. Buradaki bacth_size eğitim işlemi yapılırken fit metodunda kullanılan batch_size ile benzer anlamdadır ancak işlevleri farklıdır. Model test edilirken test işlemi de birer birer değil batch batch yapı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 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}') #---------------------------------------------------------------------------------------------------------------------------- #---------------------------------------------------------------------------------------------------------------------------- 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 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 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) 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]) 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) 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ığı ağı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') 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]}') 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') #---------------------------------------------------------------------------------------------------------------------------- Ş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. 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. 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. - 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. Çeşitli kaynaklar saklı katmanlardaki nöronların sayıları için üstünkörü şu pratik tavsiyelerde bulunmaktadır: - 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. 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. #---------------------------------------------------------------------------------------------------------------------------- #---------------------------------------------------------------------------------------------------------------------------- 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. 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. 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. 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 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') 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]}') 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') #---------------------------------------------------------------------------------------------------------------------------- Ş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. #---------------------------------------------------------------------------------------------------------------------------- #---------------------------------------------------------------------------------------------------------------------------- Ö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 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 def relu(x): return np.maximum(x, 0) x = np.linspace(-10, 10, 1000) y = relu(x) 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() #---------------------------------------------------------------------------------------------------------------------------- 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')) 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: 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. #---------------------------------------------------------------------------------------------------------------------------- import numpy as np import matplotlib.pyplot as plt 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. Pekiyi sigmoid nasıl bir fonksiyondur? Bu fonksiyona "lojistik (logistic)" fonksiyonu da denilmektedir. Fonksiyonun matematiksel ifadesi şöyledir: y = 1 / (1 + e ** -x) 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: y = e ** x / (1 + e ** x) 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() #---------------------------------------------------------------------------------------------------------------------------- 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. #---------------------------------------------------------------------------------------------------------------------------- import numpy as np import matplotlib.pyplot as plt 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: sigmoid'(x) = exp(x)/(exp(x) + 1) - exp(2 * x)/(exp(x) + 1) ** 2 #---------------------------------------------------------------------------------------------------------------------------- import sympy from sympy import init_printing init_printing() 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() #---------------------------------------------------------------------------------------------------------------------------- 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) 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. 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 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 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. Ağı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 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: 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 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 Keras aslında çıktı katmanlarındaki tüm softmax aktivasyon fonksiyonlarının bir grup oluşturduğunu varsayar. Sonra girdilerle ağırlık değelerini çarpıp bias değeriyle toplayarak yukarıdaki gibi bir x vektörü elde eder. Sonra da yukarıdaki işlemi uygular. Başka bir deyişle çıktı katmanındaki softmax aktivasyon fonksiyonuna sahip olan nöronlar bir grup olarak değerlendirilmektedir. #---------------------------------------------------------------------------------------------------------------------------- #---------------------------------------------------------------------------------------------------------------------------- Diğer çok kullanılan aktivasyon fonksiyonlarından biri de "linear" aktivasyon fonksiyonudur. Aslında bu fonksiyon y = x 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 problemlerinin çı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 regresyon problemleridir. 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: >>> 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. #---------------------------------------------------------------------------------------------------------------------------- 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() #---------------------------------------------------------------------------------------------------------------------------- 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. 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. #---------------------------------------------------------------------------------------------------------------------------- #---------------------------------------------------------------------------------------------------------------------------- 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.) 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. #---------------------------------------------------------------------------------------------------------------------------- #---------------------------------------------------------------------------------------------------------------------------- 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: mse = np.mean((y - y_hat) ** 2) Burada y gerçek değerleri, y_hat ise kestirilen değerleri belirtmektedir. Örneğin: >>> 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 Aynı işlemi tensorflow.keras.losses modülündeki mse (ya da mean_squared_error) fonksiyonuyla da aşağıdaki gibi yapabilirdik: >>> from tensorflow.keras.losses import mse >>> mse(y, y_hat) #---------------------------------------------------------------------------------------------------------------------------- #---------------------------------------------------------------------------------------------------------------------------- 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: 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 >>> 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 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: >>> 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 #---------------------------------------------------------------------------------------------------------------------------- #---------------------------------------------------------------------------------------------------------------------------- Regresyon problemleri için diğer bir loss fonksiyonu da "Mean Absolute Percentage Error (MAPE)" isimli fonksiyondur. Fonkisyonun sembolik ifadesi şöyledir: mape = 100 * np.mean(np.abs(y - y_hat) / y) 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 #---------------------------------------------------------------------------------------------------------------------------- #---------------------------------------------------------------------------------------------------------------------------- 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: 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): msle = np.mean((np.log(y + 1) - np.log(y_hat + 1)) ** 2) Ö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]) >>> 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 #---------------------------------------------------------------------------------------------------------------------------- #---------------------------------------------------------------------------------------------------------------------------- İ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 #---------------------------------------------------------------------------------------------------------------------------- #---------------------------------------------------------------------------------------------------------------------------- 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): 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. 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 ağı 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 ağı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 ağı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ı açı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öntemdir. 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ırğı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ı açı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(Bk−0.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 açı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ı açı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 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 ile 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. #---------------------------------------------------------------------------------------------------------------------------- #---------------------------------------------------------------------------------------------------------------------------- Yukarıda da belirttiğimiz gibimMetin sınıflandırma problemlerinde önce metinlerin sayısal biçime dönüştürülmesi gerekir. Metinlerin sayısal hale dönüştürülmesi için tipik olarak üç yöntem kullanılmaktadır: 1) Sözcük Çantası (Bag of words) Yöntemi. (Bu yöntem kısaca "BoW" ile temsil edilir. Biz bu yönteme "vektörizasyon" da diyeceğiz. 2) TF-IDF (Term Frequency - Inverse Document Frequencey) Yöntemi 2) Sözcük Gömme (Word Embedding) Yöntemi Bu yöntemlerin hepsinde yazılar önce faydalı bazı önişlemlere sokulur ve "atom (token) denilen küçük parçalara ayrılır. Sonra bu atomlar sayısal biçime dönüştürülür. Yazıdaki atomlar tipik olarak sözcüklerdir. Ancak yan yana birkaç sözcük de tek bir atom olarak ele alınabilmektedir. Biz burada önce en basit yöntem olan "sözcük çantası (BoW)" ve TF-IDF yöntemi üzerinde duracağız. Sözcük gömme yöntemini daha ileride ele alacağız. #---------------------------------------------------------------------------------------------------------------------------- #---------------------------------------------------------------------------------------------------------------------------- Sözcük çantası (BoW) yöntemi şö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. Her yorum bu matriste bir satır ile temsil edilmektedir. Burada 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. Bu ikili (binary) bir vektörüzasyondur. Tabii satırın sütunlarında ilgili sözcüğün yorumda kaç kere kullanıldığına ilişkin bir frekas sayısı da tutulabilir. Bu durumda satırın sütunları sözcük frekanslarından oluşacaktır. #---------------------------------------------------------------------------------------------------------------------------- #---------------------------------------------------------------------------------------------------------------------------- 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 aşağıdaki gibi ikili bir vektöre dönüştürülü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 Diğer yorumlar da aşağıdaki gibi dönüştürülecektir: 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.") Tabii 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.") BoW 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 fazla kez kullanılmış olsa da o sözcüğe ilişkin sütun elemanı 1 yapılmıştır. Ancak yukarıda da belirttiğimiz gibi 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üiasyon 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 ağı 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 geri döndürür. 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 join ile 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 BoW yöntemiyle binary bir vektöre dönüştürülmesinin görünen açık dezavantajları şunlardır: - Aynı sözcük 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. Tabii biz vektörüzasyonu sözcük frekanslarını kullanarak da yapabiliriz. Bu durumda bu dezavantaj ortadan kalkabilecektir. - BoW yöntemi 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. - BoW 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. Sınıfın __init__ metodunun parametrik yapısı şöyledir: class sklearn.feature_extraction.text.CountVectorizer(*, input='content', encoding='utf-8', decode_error='strict', strip_accents=None, lowercase=True, preprocessor=None, tokenizer=None, stop_words=None, token_pattern='(?u)\\b\\w\\w+\\b', ngram_range=(1, 1), analyzer='word', max_df=1.0, min_df=1, max_features=None, vocabulary=None, binary=False, dtype=) Metodun 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. 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=False) 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. #---------------------------------------------------------------------------------------------------------------------------- #---------------------------------------------------------------------------------------------------------------------------- Şimdi de IMDB veri kümesi için vektörizasyonu CountVectorizer sınıfı ile yapalım. Örneğimizde binary parametresini False geçeceğiz. Dolaysıyla BoW satırlarının sütunları sözcük frekanslarından oluşacaktır. #---------------------------------------------------------------------------------------------------------------------------- import pandas as pd df = pd.read_csv('IMDB Dataset.csv') from sklearn.feature_extraction.text import CountVectorizer cv = CountVectorizer(dtype='uint8', binary=False) 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 binary biçimde 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 test 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 test 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 ve binary olarak yapıp sonra CountVectorizer sınıfını kullanacağız. #---------------------------------------------------------------------------------------------------------------------------- #---------------------------------------------------------------------------------------------------------------------------- Önce "cats.txt" dosyasını açı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ğ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ılardan vektörizasyon matrisini elde etmek daha uygun olacaktır. Binary vektörizasyon işlemini 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') #---------------------------------------------------------------------------------------------------------------------------- Sözlük çantası (BoW) yönteminde bir bağlam etkisinin oluşturulması için "n-gram" denilen bir teknik de kullanılmaktadır. n-gram yazıdaki sözcükleri tek tek değil de n'li olarak atomlarına ayrılması anlamına gelir. Örneğin aşağıdaki gibi iki yazı söz konusu olsun: texts = ['Bugün hava çok güzel', 'bugün hava çok sıcak'] Burada BoW yöntemine göre sözcükler tek tek ele alınıp vektörüzasyon yapılırsa buna "unigram" denilmektedir. Biz yukarıdaki örneklerde hep "unigram" kullandık. Eğer yan yana n tane sözcük sanki tek bir atom (token) gibi ele alınırsa buna "n-gram" denilmektedir. n sayısı 2 ise bu özel olarak "bigram" biçiminde de isimlendirilmektedir. Uygulamalarda yalnızca "unigram", yalnızca "bigram" ya da hem "unigram" hem de "bigram" kullanılabilmektedir. CountVectorizer sınıfının ngram_range parametresi n-gram'daki n değerlerini belirtmektedir. Bu parametre iki elemanlı bir demet biçiminde girilir. Demetin ilk elemanından ikinci elemanına kadarki (bu değer de dahil) bütün n değerleri vektörüzasyonda kullanılır. Örneğin ngram=(1, 3) girilirse n değeir hem 1 hem 2 hem 3 anlamına gelmektedir. ngram=(2, 2) girilirse bu durum "bigram" anlamına gelir. Bu parametrenin default değeir ngram=(1, 1) biçimindedir. Yani default durumda unigram uygulanmaktadır. Aşağıdaki örneği inceleyiniz: texts = ['Bugün hava çok güzel', 'bugün hava çok sıcak'] from sklearn.feature_extraction.text import CountVectorizer cv = CountVectorizer(ngram_range=(1, 2)) result = cv.fit_transform(texts).todense() feature_names = cv.get_feature_names_out() print(feature_names) print(result) Burada biz hem "unigram" hem de "bigram" uygulamış olduk. Elde edilen çıktı şöyledir: ['bugün' 'bugün hava' 'güzel' 'hava' 'hava çok' 'sıcak' 'çok' 'çok güzel' 'çok sıcak'] [[1 1 1 1 1 0 1 1 0] [1 1 0 1 1 1 1 0 1]] Gördüğünüz gibi yazıların atomları (tokens) hem tek sözcükten hem de yan yana iki sözcükten oluşyormuş gibi vektörüzasyon yapılmıştır. Burada kullandığımız CountVectorizer sınıfının get_feature_names_out metodu sütunlara karşı gelen atomların neler olduğunu bize vermektedir. Pekiyi "unigram" uygulamakla hem "unigram" hem de "bigram" uygulamak arasındaki fark tam olarak nedir? - Yalnızca "unigram" uygulandığında daha az özellik (yani sütun) oluşturulmuş olur. Model daha basit hale gelir. Bu da eğitim zamanını kısaltır. Ancak art arda gelen sözcükler arasında bir bağlam ilişkisi ortadan kalkar. - Hem "unigram" hem "bigram" uygulanırsa daha fazla özellik söz konusu olur. Bu bazı dezavantajlar doğurabilir. Ancak peş peşe gelen iki sözcük arasında nispeten bir bağlam oluşturulmuş olur. Bağlamınm önemli olduğu veri kümesinin büyük olduğu durumlarda hem "unigram" hem de "bigram" kullanımını tercih edebilirsiniz. Aşağıdaki örnekte IMDB veri kümesi hem "unigram" hem de "bigram" biçiminde vektörize edilerek sınıflandırılmıştır: #---------------------------------------------------------------------------------------------------------------------------- import pandas as pd df = pd.read_csv('IMDB Dataset.csv') from sklearn.feature_extraction.text import CountVectorizer cv = CountVectorizer(dtype='uint8', ngram_range=(1, 2)) 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'ın TextVectorization sınıfında da n-gram kullanılabilmektedir. Sınıfın __init__ metodunun ngram parametresi None geçilirse (default durum) "unigram" anlaşılmaktadır. eğer bu parametreye belli bir n değeri geçilirse yalnızca o n değerine ilişkin n-gram işlemi yapılır. Bu parametreye bir demet girilirse demetin her elemanı bağımsız biçimde n değerlerini belirtmektedir. Örneğin: tv = TextVectorization(output_mode='count', ngrams=(1, 2)) Burada hem "unigram" hem de "bigram" vektörizasyon uygulanmaktadır. Buradaki ngrams parametresinin bir aralık belirtmediğine tek tek değerler belirttiğine dikkat ediniz. Aşağıda IMDB örneği TextVectorization sınıfı kullanılarak hem "unigram" hem de "bigram" vektörizasyonla gerçekleştirilmiştir. #---------------------------------------------------------------------------------------------------------------------------- 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', ngrams=(1, 2)) 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') #---------------------------------------------------------------------------------------------------------------------------- Metinlerin sınıflandırılmasında yazıların sayısal biçime dönüştürülmesi sürecinde kullanılan diğer bir yöntem de "TF-IDF (Term Frequency - Inverse Document Frequence)" yöntemidir. Bu yöntemde tüm atomlara (tipik olarak sözcük haznesindeki tüm sözcüklere) birer TF-IDF değeri karşılık getirilir. Böylece yorumlar birer TF-IDF vektörüne dönüştürülür. TF-IDF yönteminde her sözcük için önce TF ve IDF değerleri hesaplanır. Bu iki değer çarpılarak TF-IDF değeri elde edilir:, TF-IDF = TF * IDF Bir sözcüğün TF değeri şöyle hesaplanmaktadır: TF = sözcüğün yorumdaki sayısı / yorumdaki sözcük sayısı Örneğin yorum şöyle olsun: "Bu film çok güzel. Film aynı zamanda çok güzel yerlerde çekilmiş." Yorumda toplam 11 sözcük var. Bu durumda "film", "çok", "güzel" sözcüklerinin TF değerleri 2/11 diğer sözcüklerin TF değerleri ise 1/11 olur. Sözcüklerin IDF değerleri ise şöyle hesaplanmaktadır: IDF = log (N / toplam yorum sayısı) Buradaki N değeri ilgili sözcüğün geçtiği farklı yorum sayısını belirtmektedir. Örneğin "muhteşem" sözcüğü 5 farklı yorumda geçiyor olsun. Toplamda da 100 tane yorum olsun. Bu durumda "muhteşem" sözcüğünün IDF değeri 5 / 100 olacaktır. Yukarıda da belirttiğimiz gibi sözcüğün TF-IDF değeri TF değeri ile IDF değerinin çarpımından elde edilmektedir. #---------------------------------------------------------------------------------------------------------------------------- #---------------------------------------------------------------------------------------------------------------------------- TF-IDF yönteminde yine tüm atomlar için (sözcük haznesindeki tüm sözcükler için) bir TF-IDF değeri elde edilir. Böylece tüm yorumlar sözcük haznesi uzunluğu kadar sütuna, yorum sayısı kadar satıra sahip olan bir matrisle temsil edilir. Tabii bu matris de yine seyrek (sparse) biçimde olur. Çünkü bir yorumda sözcük haznesindeki çok az sözcük kullanılmış olacaktır. #---------------------------------------------------------------------------------------------------------------------------- #---------------------------------------------------------------------------------------------------------------------------- Yazıların TF-IDF yöntemi ile vektörize edilmesi scikit-learn içerisinde TfidfVectorizer sınıfı kullanılarak yapılabilmektedir. Sınıfın kullanımı diğer scikit-learn sınıflarında olduğu gibidir. TfidfVectorizer sınıfının __init__ metodunun parametrik yapısı şöyledir: class sklearn.feature_extraction.text.TfidfVectorizer(*, input='content', encoding='utf-8', decode_error='strict', strip_accents=None, lowercase=True, preprocessor=None, tokenizer=None, analyzer='word', stop_words=None, token_pattern='(?u)\\b\\w\\w+\\b', ngram_range=(1, 1), max_df=1.0, min_df=1, max_features=None, vocabulary=None, binary=False, dtype=, norm='l2', use_idf=True, smooth_idf=True, sublinear_tf=False) #---------------------------------------------------------------------------------------------------------------------------- #---------------------------------------------------------------------------------------------------------------------------- 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 "ü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ı açı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') 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 ü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): for _ in range(VALIDATION_STEPS): 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) #---------------------------------------------------------------------------------------------------------------------------- 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 #---------------------------------------------------------------------------------------------------------------------------- #---------------------------------------------------------------------------------------------------------------------------- Ş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 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. 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 #---------------------------------------------------------------------------------------------------------------------------- #---------------------------------------------------------------------------------------------------------------------------- Örnek ü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 yazdık. Program şöyldir: # convert_imdb.py 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: 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: 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. #---------------------------------------------------------------------------------------------------------------------------- import math import random import csv import pandas as pd import tensorflow as tf EPOCHS = 5 BATCH_SIZE = 32 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') from tensorflow.keras import Sequential from tensorflow.keras.layers import Input, Dense, TextVectorization 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')) model.summary() 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) 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() # 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') #---------------------------------------------------------------------------------------------------------------------------- #---------------------------------------------------------------------------------------------------------------------------- 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: model.fit(..., validation_data=validation_generator(), validation_steps=100) Burada sınama verilerinin elde edilmesi için toplam 100 kez dolaşım (yield işlemi) yapılacaktır. Her epoch sonrasındaki 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: 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: 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) 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 from tensorflow.keras.layers import Input, Dense, TextVectorization 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')) model.summary() model.compile(optimizer='rmsprop', loss='binary_crossentropy', metrics=['binary_accuracy']) 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 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(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]}') # 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(predict_generator(predict_f, predict_steps, BATCH_SIZE, predict_offsets), steps=predict_steps) for presult in predict_result[:, 0]: if (presult > 0.5): print('Positive') else: print('Negative') #---------------------------------------------------------------------------------------------------------------------------- 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ı.) 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: 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. 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. 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. 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 from tensorflow.keras import Sequential from tensorflow.keras.layers import Input, Dense from tensorflow.keras.utils import PyDataset EPOCHS = 2 NFEATURES = 10 BATCH_SIZE = 32 class DataGenerator(PyDataset): def __init__(self, batch_size, nfeatures, *, steps): super().__init__() self.batch_size = batch_size self.nfeatures = nfeatures self.steps = steps def __len__(self): return self.steps 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): 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_epoch_end(self): random.shuffle(self.offsets) df = pd.read_csv('imdb.csv') from tensorflow.keras import Sequential from tensorflow.keras.layers import Input, Dense, TextVectorization 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')) model.summary() model.compile(optimizer='rmsprop', loss='binary_crossentropy', metrics=['binary_accuracy']) 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 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(f, test_steps, BATCH_SIZE, test_offsets)) for i in range(len(eval_result)): print(f'{model.metrics_names[i]}: {eval_result[i]}') # 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') #---------------------------------------------------------------------------------------------------------------------------- Aslında Keras'ta uzunca bir süredir hem dizin içerisindeki resimlerden önişlem yapan, veri artırımı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. üç flow metodu vardır: flow flow_from_dataframe flow_from_directory 3) Artık model kurulup fit işleminde x verileri 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ı açıklanmaktadır. #---------------------------------------------------------------------------------------------------------------------------- #---------------------------------------------------------------------------------------------------------------------------- Aslında parçalı eğitimler için kullanılan asıl sınıf Dataset isimli sınıftır. Ancak bu sınıf Tensorflow kütüphanesinin aşağı seviyeli çalışması ile uyumlu biçimde hazırlanmıştır. Bu sınıfın Keras'tan kolay kullanılabilmesi için PyDataset sınıfı oluşturulmuştur. Biz de yukarıdaki örneklerimizde PyDataset sınıfından türetme yaparak işlemlerimizi gerçekleştirdik. Tensorflow'un aşağı seviyeli Dataset sınıfı tensorflow.data modülü içerisindedir. Bu Dataset sınıfı soyut bir sınıftır. Yani bunun kullanılması için bu sınıftan türetme yapılıp türemiş sınıfta pek çok metodun yazılması gerekir. Zaten bun nedenden dolayı kullanımı basitleştirmek için PyDataset sınıfı oluşturulmuştur. Ancak Tensorflow kütüphanesinde uygulamacı için işlemleri kolaylaştıran yardımcı fonksiyonlar ve sınıflar da bulundurulmuştur. Bu fonksiyonlar birtakım işlemleri kolaylaştıran Dataset nesneleri oluşturmaktadır. Örneğin image_dataset_from_directory fonksiyonu eğer resimler bir dizin içerisinde uygun bir biçimde yerleştirilmişse onları batch batch dizinlerden alarak parçalı bir biçimde dışarıya verebilmektedir. Böylece uygulamacı resimleri bir dizine uygun biçimde yerleştirdikten sonra doğrudan bu fonksiyonun çıktısı olan DataSet nesnesini fit, evaluate ve predict gibi metotlara verebilmektedir. İzleyen paragraflarda işlemleri kolaylaştıran ve bize parçalı eğitim için kullanabileceğimiz Dataset nesneleri veren bazı fonksiyonlar ve sınıfları göreceğiz. Bu fonksiyonlar Tensorflow kütüphanesine göreli olarak yeni eklenmiştir. #---------------------------------------------------------------------------------------------------------------------------- #---------------------------------------------------------------------------------------------------------------------------- tensorflow.data modülündeki Dataset sınıfı soyut bir sınıftır. Yani bu sınıfı biz doğrudan kullanamayız. Bu sınıftan türetme yapılıp türemiş sınıfta taban Dataset sınıfının metotlarının override edilmesi gerekir. Tabii pratikte programcılar türetme yapmak yerine zaten hazırda var olan somut türemiş sınıfları ya da somut türemiş sınıf nesnelerini veren fonksiyonları kullanmaktadır. Biz burada Dataset sınıfında bulunan birkaç önemli fonksiyondan bahsedeceğiz. Dataset sınıfları dolaşılabilir sınıflardır. Biz bir Dataset nesnesini dolaştığımızd batch batch bilgileri elde ederiz. Sınıfın take metodunun parametrik yapısı şöyledir: take( count, name=None ) Metodun count parametresi Dataset nesnesinden kaç batch'lik alınacağını belirtmektedir. Bu parametre -1 girilirse tüm elemanlar elde edilmektedir. take metodu bize bir dolaşım nesnesi (iterator) verir. Bu dolaşım nesnesi her dolaşıldığında x ve y değerlerinden oluşan demetler elde edilmektedir. Bize verilen bu dolaşım nesnesi toplamda count defa dolaşılmaktadır. Örneğin Dataset nesnesi için belirlenmiş olan batch_size 32 olsun. take metodunda count parametresi için 10 girersek bu nesneyi her dolaştığımızda 32'lik bir x ve y veri kümesi elde ederiz. Toplamda da bu nesneyi 10 kez dolaşabiliriz. Dataset sınıfının batch isimli metodu bizden bir batch_size değeri alır ve bize bir dolaşım nesnesi verir. Biz bu dolaşım nesnesini dolaştığımızda batch_size kadar batch'ler elde ederiz. Metodun parametrik yapısı şöyledir: batch( batch_size, drop_remainder=False, num_parallel_calls=None, deterministic=None, name=None ) #---------------------------------------------------------------------------------------------------------------------------- #---------------------------------------------------------------------------------------------------------------------------- Şimdi de bize Dataset nesneleri veren hazır fonksiyonlardan ve sınıflardan bahsedelim. Yukarıda da belirttiğimiz gibi tensorflow.keras.preprocessing modülündeki image_dataset_from_directory isimli fonksiyon bir dizinden hareketle oradaki esimleri 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. 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 ) Bu fonksiyonu kullanmadan önce 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. Resimlerin bulunduğu dizinin yol ifadesini fonksiyonun birinci parametresine geçirir. İkinci parametre sınıf belirten etiketlerin nasıl oluşturulacağını belirtmektedir. Buradaki default 'inferred' değeri etiketlerin otomatik olarak dizin yapısından oluşturulacağı anlamına gelmektedir. Bu parametre birkaç biçimde daha geçilebilmektedir. Bunun için dokümanlara 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 burada y değerleri "one-hot-encoding" biçiminde oluşturulur. Eğer bu parametreye 'binary' girilirse y değerleri 0, 1 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 edilir. 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 karıştırılıp karıştırılmayacağını belirtmektedir. Bu parametrenin default değerinin True olduğunu görüyorsunuz. Fonksiyonun diğer parametreleri 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=32) Artık image_dataset_from_dirictory fonksiyonuyla elde ettiğimiz Dataset nesnesini daha önce görmüş olduğumuz parçalı verilerle eğitimde kullanabiliriz. Bir Dataset nesnesi içerisindeki bilgiler iteratör yoluyla, sınıfın batch ya da take metotları yoluyla elde edilebildiğini anımsayınız. Biz image_dataset_from_directory fonksiyonunu yalnız fit işlemlerinde değil, test ve kestirim işlemlerinde de kullanabiliriz. Fonksiyonun seubset parametresi 'training', 'validation' ya da 'both' biçiminde girilebilmektedir. 'training' eldeki resim kümesindenki bir grup resmin eğitim amacıylai 'validation' ise sınama amacıyla kullanılacağını belirtmektedir. Ancak subset parametresi girildiğinde validation_split paranetresinin ve seed parametresinin de girilmesi gerekmektedir. Örneğin biz eğitim ve sınama kümeleri için ayrışırmayı şöyle yapabiliriz: training_dataset = image_dataset_from_directory('Images', label_mode='binary', image_size=(128, 128), subset='training', seed=123, validation_split=0.2, batch_size=32) validation_dataset = image_dataset_from_directory('Images', label_mode='binary', image_size=(128, 128), subset='training', seed=123, validation_split=0.2, batch_size=32) Bu biçimdeki kullanımda seed değerlerinin aynı girilmesi gerekmektedir. #---------------------------------------------------------------------------------------------------------------------------- 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(10) 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() #---------------------------------------------------------------------------------------------------------------------------- tensorflow.data modülündeki TextLineDataset isimli sınıf bir ya da birden fazla dosyanın satırlarını parçalı bir biçimde dışarıya verebilmektedir. Yani bu sınıfı bir dosyanın satırlarını batch batch elde eden bir Dataset sınıfı olarak düşünebiliriz. Zaten TextLineDataset sınıfı Dataset sınıfından türetilmiş durumdadır. Sınıfın as_numpy_iterator metodu bize satırları NumPy nesneleri olarka verebilmektedir. Örneğin: from tensorflow.data import TextLineDataset tld = TextLineDataset('iris.txt') for line in tld.as_numpy_iterator(): print(line) tld.batch() Sınıfın ayrıntıları için ilgili dokümanlara başvurulabilir. #---------------------------------------------------------------------------------------------------------------------------- #---------------------------------------------------------------------------------------------------------------------------- tensorflow.data modülü içerisindeki Dataset sınıfının list_files isimli static metodu bir dizinde joker karakterleriyle verilmiş olan dosyaları bize Dataset nesnesi biçiminde vermektedir. Örneğin: dataset = tensorflow.data.Dataset.list_files("/path/*.txt") Tabii buradan elde edilen Dataset nesnesi dosyanın içeriğini değil yalnızca dosyaların yol ifadelerini vermektedir. Dataset sınıfının from_generator isimli static metodu bir üretici fonksiyonu alıp onu Dataset nesnesi haline getirmektedir. Yani bu fonksiyon elimizde bir üretici fonksiyon varsa ancak bizden bir Dataset nesnesi isteniyorsa o üretici fonksiyonu Dataset nesnesi haline getirmek için kullanılmaktadır. Dataset sınıfının from_tensor_slices isimli static metodu ise bizden bir Tensor nesnesi ya da dolaşılabilir bir nesne alarak ondan Dataset nesnesi oluşturmaktadır. Örneğin: dataset = Dataset.from_tensor_slices([1, 2, 3, 4, 5]) for val in dataset.as_numpy_iterator(): print(val) tensorflow.data.experimental modülü içerisindeki make_csv_dataset fonksiyonu bizden bir CSV dosyasını alıp ondan parçalı biçimde satır elde etmekte kullanılmaktadır. Örneğin: from tensorflow.data.experimental import make_csv_dataset dataset = make_csv_dataset('iris.csv', batch_size=32, label_name='Species') #---------------------------------------------------------------------------------------------------------------------------- #---------------------------------------------------------------------------------------------------------------------------- 49. Ders - 13/07/2024 - Cumartesi #---------------------------------------------------------------------------------------------------------------------------- #---------------------------------------------------------------------------------------------------------------------------- Pekiyi biz parçalı eğitimi fit metodunu birden fazla kez çağırarak yapamaz mıyız? Keras'ın orijinal dokümanlarında bu 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: train_on_batch test_on_batch predict_on_batch 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. train_on_batch metodunun parametrik yapısı şöyledir: train_on_batch(x, y=None, sample_weight=None, class_weight=None, return_dict=False) 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. 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: for epoch in range(EPOCHS): for batch_no in range(NBATCHES): 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: for epoch in range(EPOCHS): for batch_no in range(NBATCHES): rd = model.train_on_batch(x, y, return_dict=True) 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. 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: for epoch in range(EPOCHS): for batch_no in range(NBATCHES): 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: test_on_batch(x, y=None, sample_weight=None, return_dict=False) Kullanım tamamen train_batch metodu gibidir. 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: predict_on_batch(x) Metot kestirim değerleriyle geri dönmektedir. Örneğin: for batch_no in range(NBATCHES): predict_result = model.predict_on_batch(x) #---------------------------------------------------------------------------------------------------------------------------- #---------------------------------------------------------------------------------------------------------------------------- 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. #---------------------------------------------------------------------------------------------------------------------------- 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']) 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() 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) print('-' * 30) 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}') 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) import matplotlib.pyplot as plt 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() 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() # evaluation 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}') # prediction predict_df = pd.read_csv('predict-imdb.csv') 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. #---------------------------------------------------------------------------------------------------------------------------- #---------------------------------------------------------------------------------------------------------------------------- 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 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. #---------------------------------------------------------------------------------------------------------------------------- #---------------------------------------------------------------------------------------------------------------------------- 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: {(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: 0 0 5 3 0 0 0 6 0 Buradaki dok sözlüğü şöyle olacaktır: {(0, 2): 5, (1, 0): 3, (2, 1): 6} 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: def __init__(self, nrows, ncols): self._nrows = nrows self._ncols = ncols self._dok = {} def __getitem__(self, index): 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') 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') 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): 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 return smatrix 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)) #---------------------------------------------------------------------------------------------------------------------------- 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. #---------------------------------------------------------------------------------------------------------------------------- #---------------------------------------------------------------------------------------------------------------------------- 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 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 ç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) #---------------------------------------------------------------------------------------------------------------------------- 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 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. 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. 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 ö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 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 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) #---------------------------------------------------------------------------------------------------------------------------- 51. Ders - 20/07/2024 - Cumartesi #---------------------------------------------------------------------------------------------------------------------------- #---------------------------------------------------------------------------------------------------------------------------- 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] #---------------------------------------------------------------------------------------------------------------------------- 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 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. #---------------------------------------------------------------------------------------------------------------------------- import numpy as np from scipy.sparse import csc_matrix 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) lil = dok.tolil() print(lil) #---------------------------------------------------------------------------------------------------------------------------- Seyrek bir matris train_test_split fonksiyonuyla ayrıştırılabilir. Çünkü zaten train_test_split fonksiyonu dilimleme yoluyla işlemlerini yapmaktadır. Ancak seyrek matrislere len fonksiyonu uygulanamaz. 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. #---------------------------------------------------------------------------------------------------------------------------- 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 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. - 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. 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. #---------------------------------------------------------------------------------------------------------------------------- #---------------------------------------------------------------------------------------------------------------------------- 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) print(type(result)) print(result.todense()) #---------------------------------------------------------------------------------------------------------------------------- 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 texts = ['this film is very very good', 'I hate this film', 'It is good', 'I don\'t like it'] cv = CountVectorizer() cv.fit(texts) result = cv.transform(texts) print(result) print(result.todense()) #---------------------------------------------------------------------------------------------------------------------------- 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) Burada karıştırma sonrasında x'in aynı satırları yine y'nin aynı satırlarına karşı gelecektir. #---------------------------------------------------------------------------------------------------------------------------- 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? 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. #---------------------------------------------------------------------------------------------------------------------------- #---------------------------------------------------------------------------------------------------------------------------- Ş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. #---------------------------------------------------------------------------------------------------------------------------- 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 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 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 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 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') #---------------------------------------------------------------------------------------------------------------------------- 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']) 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(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 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. 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 ağı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() #---------------------------------------------------------------------------------------------------------------------------- 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 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 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) # minmax scaling scaled_training_dataset_x = training_dataset_x / 255 scaled_test_dataset_x = test_dataset_x / 255 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].reshape(28, 28) plt.imshow(picture, cmap='gray') plt.show() """ 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() """ """ 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() """ 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() 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() 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]).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 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ırç #---------------------------------------------------------------------------------------------------------------------------- from tensorflow.keras.datasets import mnist (training_dataset_x, training_dataset_y), (test_dataset_x, test_dataset_y) = mnist.load_data() training_dataset_x = training_dataset_x.reshape(-1, 28 * 28) test_dataset_x = test_dataset_x.reshape(-1, 28 * 28) # 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) # minmax scaling scaled_training_dataset_x = training_dataset_x / 255 scaled_test_dataset_x = test_dataset_x / 255 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].reshape(28, 28) plt.imshow(picture, cmap='gray') plt.show() """ 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() """ """ 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() """ 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() 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() 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]).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}') #---------------------------------------------------------------------------------------------------------------------------- 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 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 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 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 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 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 Ö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 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. Toplam Padding Genişliği = Kernel Genişliği - 1 Toplam Padding Yüksekliği = Kernel Yüksekliği - 1 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 Asl resmin padding'li hali şu biçimde görünecektir: 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 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 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: 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 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 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: 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 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 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: 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: 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. #---------------------------------------------------------------------------------------------------------------------------- #---------------------------------------------------------------------------------------------------------------------------- 55. Ders - 03/08/2024 - Cumartesi #---------------------------------------------------------------------------------------------------------------------------- #---------------------------------------------------------------------------------------------------------------------------- Biz yukarıda evrişim işleminin gri tonlamalı resimlerde nasıl yapıldığını açı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. #---------------------------------------------------------------------------------------------------------------------------- #---------------------------------------------------------------------------------------------------------------------------- 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. 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. #---------------------------------------------------------------------------------------------------------------------------- #---------------------------------------------------------------------------------------------------------------------------- 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 ağı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. #---------------------------------------------------------------------------------------------------------------------------- #---------------------------------------------------------------------------------------------------------------------------- Ş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: 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: conv2 = Conv2D(32, (3, 3), padding='same', activation='linear') Burada toplam 32 filtre uygulanmıştır. Kernel (3, 3) olarak alınmıştır. padding parametresi default "valid" durumdadır. Bu "valid" değeri "padding yapılmayacağı" anlamına gelir. Bu parametre "same" geçilirse padding yapılır. Yani hedef resim kaynak resimle aynı büyüklükte olur. 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. #---------------------------------------------------------------------------------------------------------------------------- #---------------------------------------------------------------------------------------------------------------------------- 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') 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() 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. Bu modelden şöyle bir özet elde edilmiştir: Model: "MNIST" ┌─────────────────────────────────┬────────────────────────┬───────────────┐ │ Layer (type) │ Output Shape │ Param # │ ├─────────────────────────────────┼────────────────────────┼───────────────┤ │ Conv2D-1 (Conv2D) │ (None, 26, 26, 32) │ 320 │ ├─────────────────────────────────┼────────────────────────┼───────────────┤ │ Conv2D-2 (Conv2D) │ (None, 24, 24, 64) │ 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 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, 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() 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() 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}') #---------------------------------------------------------------------------------------------------------------------------- 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. - 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. 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 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 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 Bu işlemin sonucunda elde edilen matrisin ilkinin karekökü kadar olduğuna dikkat ediniz. Yani pooling çerçevesi resmi üstel olarak küçültmektedir. 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. #---------------------------------------------------------------------------------------------------------------------------- #---------------------------------------------------------------------------------------------------------------------------- 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) 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. 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() Modelden şöyle bir özet bilgi edilmiştir: 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. #---------------------------------------------------------------------------------------------------------------------------- #---------------------------------------------------------------------------------------------------------------------------- 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'] 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: 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'] 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 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 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='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() 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 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. 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. #---------------------------------------------------------------------------------------------------------------------------- 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'] 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, 0]], 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='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() 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 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 Buradaki dosya indirilip açıldığında "cifar-100-python" dizini içerisinde "train" ve "test" isimli iki dosya bulunacaktır. Bu dosyalar yine Python'un 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. #---------------------------------------------------------------------------------------------------------------------------- import pickle import numpy as np EPOCHS = 5 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.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 = [ '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=(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 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() #---------------------------------------------------------------------------------------------------------------------------- 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. #---------------------------------------------------------------------------------------------------------------------------- #---------------------------------------------------------------------------------------------------------------------------- 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. #---------------------------------------------------------------------------------------------------------------------------- #---------------------------------------------------------------------------------------------------------------------------- Resimsel verilerin artırılması için pek çok teknik kullanılmaktadır. Önemli teknikler şunlardır: - 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 Saturation): 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 getirilerek 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 tekniklerle çoğaltıp onları saklamak mı yoksa eğitime sokarken onları hiç saklamadan o anda ç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: RandomFlip RandomRotation RandomZoom RandomContrast RandomCrop RandomBrightness RandomTranslation Resize Rescaling 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 açı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. Aşağıda bu sınıfların yarattığı etkileri gösteren bir örnek verilmiştir. #---------------------------------------------------------------------------------------------------------------------------- import matplotlib.pyplot as plt picture = plt.imread('Sample-Pictures/AbbeyRoad.jpg') plt.figure(figsize=(9, 16)) plt.imshow(picture); plt.show() 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) 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=(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() 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, 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')) 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 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 #---------------------------------------------------------------------------------------------------------------------------- #---------------------------------------------------------------------------------------------------------------------------- 60. Ders - 07/09/2024 - Cumartesi #---------------------------------------------------------------------------------------------------------------------------- #---------------------------------------------------------------------------------------------------------------------------- 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. - 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. #---------------------------------------------------------------------------------------------------------------------------- #---------------------------------------------------------------------------------------------------------------------------- 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, restore_best_weights=False, start_from_epoch=0 ) 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. Aşağıdaki örnekte Boston Housing Prices 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: ... 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 EPOCHS = 200 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 from tensorflow.keras.callbacks import 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']) 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 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]}') 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) #---------------------------------------------------------------------------------------------------------------------------- #---------------------------------------------------------------------------------------------------------------------------- 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. #---------------------------------------------------------------------------------------------------------------------------- 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'] 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, 0]], 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 from tensorflow.keras.callbacks import EarlyStopping 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() model.compile('rmsprop', loss='categorical_crossentropy', metrics=['categorical_accuracy']) 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]) 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 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', save_freq='epoch', initial_value_threshold=None ) 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: mcp = ModelCheckpoint('boston-checkpoint.keras', monitor='val_loss', save_best_only=True) Burada save_best_only parametresi True girildiği için yalnızca en iyi model save edilecektir. Bu callback sınıfının amacı eğitimi erkenden sonlandırmak değildir. 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: - 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. - 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. - Metodun birinci parametresine kalıp girilmezse ve save_best_only parametresi False geçilirse: Bu durumda son epoch'taki değerler save edilir. 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. 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: mcp = ModelCheckpoint('boston-checkpoint-{epoch:03d}.keras', monitor='val_loss', save_best_only=True) Burada "val_loss" metrik değeri daha öncekilere göre iyi olan epoch ile karşılaşıldığında "boston-checkpoint-NNN" gibi (burada NN epoch numarasını belirtir) bir dosyaya save işlemi yapılacaktır. #---------------------------------------------------------------------------------------------------------------------------- #---------------------------------------------------------------------------------------------------------------------------- 61. Ders - 08/09/2024 - Pazar #---------------------------------------------------------------------------------------------------------------------------- #---------------------------------------------------------------------------------------------------------------------------- Aşağıdaki örnekte "Boston Housing Prices" veri kümesinde EarlyStopping ve ModelCheckpoint sınıfları bir arada kullanılmıştır. Kodun ilgili kısmı şöyledir: 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]) 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. #---------------------------------------------------------------------------------------------------------------------------- import pandas as pd EPOCHS = 200 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=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 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() 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]}') 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) #---------------------------------------------------------------------------------------------------------------------------- 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. #---------------------------------------------------------------------------------------------------------------------------- #---------------------------------------------------------------------------------------------------------------------------- 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. 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: 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. 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. Girdiler ---> önceden eğitilmiş model ---> uygulamacının kendi amaçları için oluşturduğu model ---> çıktılar 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. #---------------------------------------------------------------------------------------------------------------------------- #---------------------------------------------------------------------------------------------------------------------------- Keras'ın Sequential modelinde Sequential sınıfının add metoduyla modele katman nesnelerini ekliyorduk. Ancak bu katman nesneleri hep modelin sonuna ekleniyordu. Ayrıca Sequential modelde yalnızca bir tane girdi katmanı ve yalnızca bir tane çıktı katmanı bulunabiliyordu. Oysa bazı uygulamalarda girdiler ve çıktılar birden fazla çeşit olabilmektedir. Örneğin ağın girdisi hem bir resim hem de bir yazı 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. #---------------------------------------------------------------------------------------------------------------------------- #---------------------------------------------------------------------------------------------------------------------------- 62. Ders - 14/09/2024 - Cumartesi #---------------------------------------------------------------------------------------------------------------------------- #---------------------------------------------------------------------------------------------------------------------------- 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: dense1 = Dense(256, activation='relu', name='Dense-1') dense2 = Dense(256, activation='relu', name='Dense-2') 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: result = dense1(data) Şimdi bu çıktıyı biz diğer Dense katman nesnesine girdi olarak verebiliriz: result = dense2(result) Yani aslında Sequential model yukarıdaki gibi bir katmanın çıktısı diğer katmana girdi yapılarak oluşturulmuştur. Örneğin: inp = Input(...) d1 = Dense(...) d2 = Dense(...) d3 = Dense(...) d4 = Dense(...) result = d1(inp) result = d2(result) result = d3(result) out = d4(result) Yukarıdaki işlemleri daha kompakt olarak aşağıdaki gibi de yapabiliriz: inp = Input(...) result = Dense(...)(inp) result = Dense(...)(result) result = Dense(...)(result) out = Dense(...)(result) 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. Ö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: import numpy as np from tensorflow.keras.layers import Dense data = np.random.random((32, 8)) d = Dense(16, activation='relu', name='Dense') result = d(data).numpy() 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. Fonksiyonel olarak oluşturduğumuz yapıya dikkat ediniz: inp = Input(...) result = Dense(...)(inp) result = Dense(...)(result) result = Dense(...)(result) out = Dense(result) 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: model = Model(inputs=inp, outputs=out, name='MyModel') Aslında burada oluşturmaya çalıştığımız modelin Sequential eşdeğeri şöyledir: model = Sequential('MyModel') model.add(Input(...)) model.add(Dense(...) model.add(Dense(...)) model.add(Dense(...)) model.add(Dense(...)) 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: 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. #---------------------------------------------------------------------------------------------------------------------------- #---------------------------------------------------------------------------------------------------------------------------- Şimdi daha önce yapmış olduğumuz "iris" örneğini fonksiyonel modelle yeniden yapalım. Model şöyle kurulabilir: from tensorflow.keras import Model from tensorflow.keras.layers import Input, Dense 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) model = Model(inputs=inp, outputs=out, name='FunctionalModel') 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. #---------------------------------------------------------------------------------------------------------------------------- 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_output= 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 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(dataset_y.shape[1], activation='softmax', name='Output')(result) model = Model(inputs=inp, outputs=out, name='FunctionalModel') 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) """ #---------------------------------------------------------------------------------------------------------------------------- #---------------------------------------------------------------------------------------------------------------------------- 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) #---------------------------------------------------------------------------------------------------------------------------- Bir kestirim modelinde veriler farklı alanlara ilişkin olabilir. Örneğin veri kümesindeki sütunlardan biri bir yazı olabilir, 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ümerik 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şturulabilmektedir. Ö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 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" denilen tekniktir. Biz şimdiye kadar IMDB ve Reuters 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? 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 birbirine 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'un "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 ağı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 ) 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: ... 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: ... model.add(Input((100, ))) model.add(Embedding(30000, 32)) ... 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. 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. 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. 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: 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') 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ğı 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. Ş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: TEXT_SIZE = 250 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']) 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: 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 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: 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') Geri kalan işlemler benzer biçimde yürütülecektir. Aşağıda örneği bir bütün haline veriyoruz. #---------------------------------------------------------------------------------------------------------------------------- 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') #---------------------------------------------------------------------------------------------------------------------------- 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ımsatmak 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 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') 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 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') #---------------------------------------------------------------------------------------------------------------------------- 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 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/ Benzer çalışmalar başka kurumlar tarafından da yapılmıştır. Internet'te çeşitli alternatifleri bulabilirsiniz. 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: .... 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: 2000000 300 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. #---------------------------------------------------------------------------------------------------------------------------- #---------------------------------------------------------------------------------------------------------------------------- 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 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')) 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 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 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')) 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') #---------------------------------------------------------------------------------------------------------------------------- İç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: 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. 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: TextVectorization --> Embedding --> Conv1D --> Flatten/Reshape --> Dense --> Dense --> Dense (Output) Model Keras'ta aşağıdaki gibi oluşturulmuştur: 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, 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() Burada görüldüğü gibi Embedding katmanından sonra Conv1D katmanı uygulanmıştır. Modelin özet (summary) görünümü şöyledir: 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. #---------------------------------------------------------------------------------------------------------------------------- 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. #---------------------------------------------------------------------------------------------------------------------------- #---------------------------------------------------------------------------------------------------------------------------- 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 izleyen 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ıcaklığı 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. 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ırılması için 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) from sklearn.preprocessing import OneHotEncoder ohe = OneHotEncoder(sparse_output=False) ohe.fit(df[['Month', 'Hour-Minute']]) ohe_result = ohe.transform(df[['Month', 'Hour-Minute']]) df = pd.concat([df, pd.DataFrame(ohe_result)], axis=1) df.drop(['Month', 'Hour-Minute'], axis=1, inplace=True) x = df.to_numpy('float32') y = df['T (degC)'].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 burada dataset_x ve dataset_y verilerini aşağıdaki ayırabiliriz: dataset_x = x[:-PREDICTION_INTERVAL] dataset_y = y[PREDICTION_INTERVAL:] Burada her satır PREDICTION_INTERVAL kadar ileridki satırda bulunan y değeri ile eşleştirilmiştir. Veri kümesi ayrıştırıldıktan sonra artık özellik ölçeklemesi yapabiliriz: 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, shuffle=False) 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) Modelimiz aşağıdaki gibi olabilir: from tensorflow.keras import Sequential from tensorflow.keras.layers import Input, Dense model = Sequential(name='Jena-Climate') model.add(Input((scaled_training_dataset_x.shape[1], ), name='Input')) model.add(Dense(256, activation='relu', name='Hidden-1')) model.add(Dense(256, activation='relu', name='Hidden-2')) model.add(Dense(1, activation='linear', name='Output')) model.summary() Örnek bir bütün olarak aşağıda verilmiştir. #---------------------------------------------------------------------------------------------------------------------------- import pandas as pd PREDICTION_INTERVAL = 24 * 60 // 10 # 144 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) from sklearn.preprocessing import OneHotEncoder ohe = OneHotEncoder(sparse_output=False) ohe.fit(df[['Month', 'Hour-Minute']]) ohe_result = ohe.transform(df[['Month', 'Hour-Minute']]) df = pd.concat([df, pd.DataFrame(ohe_result)], axis=1) df.drop(['Month', 'Hour-Minute'], axis=1, inplace=True) x = df.to_numpy('float32') y = df['T (degC)'].to_numpy('float32') dataset_x = x[:-PREDICTION_INTERVAL] dataset_y = y[PREDICTION_INTERVAL:] 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, shuffle=False) 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='Jena-Climate') model.add(Input((scaled_training_dataset_x.shape[1], ), name='Input')) model.add(Dense(256, activation='relu', name='Hidden-1')) model.add(Dense(256, activation='relu', name='Hidden-2')) model.add(Dense(1, activation='linear', name='Output')) model.summary() model.compile(optimizer='rmsprop', loss='mean_squared_error', metrics=['mean_absolute_error']) from tensorflow.keras.callbacks import EarlyStopping esc = EarlyStopping(monitor="val_loss", patience=5, restore_best_weights=True) hist = model.fit(scaled_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 - Mean Squared Error', pad=10, fontsize=14) plt.xticks(range(0, 300, 10)) plt.plot(hist.epoch, hist.history['mean_absolute_error']) plt.plot(hist.epoch, hist.history['val_mean_absolute_error']) plt.legend(['MSE', 'Validation MSE']) plt.show() # evaluation 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]}') # prediction predict_df = pd.read_csv('predict.csv') predict_df['Month'] = predict_df['Date Time'].str[3:5] predict_df['Hour-Minute'] = predict_df['Date Time'].str[11:16] predict_df.drop(['Date Time'], axis=1, inplace=True) ohe_result = ohe.transform(predict_df[['Month', 'Hour-Minute']]) predict_df = pd.concat([predict_df, pd.DataFrame(ohe_result)], axis=1) predict_df.drop(['Month', 'Hour-Minute'], axis=1, inplace=True) predict_dataset = predict_df.to_numpy('float32') scaled_predict_dataset = ss.transform(predict_dataset) predict_result = model.predict(scaled_predict_dataset) for presult in predict_result[:, 0]: print(presult) #---------------------------------------------------------------------------------------------------------------------------- Şimdi de Jena Climate örneğini tek boyutlu evrişim katmanı ile gerçekleştirelim. 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: .... 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. #---------------------------------------------------------------------------------------------------------------------------- #---------------------------------------------------------------------------------------------------------------------------- 70. Ders - 12/10/2024 - Cumartesi #---------------------------------------------------------------------------------------------------------------------------- #---------------------------------------------------------------------------------------------------------------------------- Şimdi Jena Climate örneğini yukarıda açıklandığı gibi gerçekleştirelim. Programımızda üç önemli değer kullanacğız: PREDICTION_INTERVAL = 24 * 60 // 10 # 144 WINDOW_SIZE = 24 * 60 // 10 # 144 SLIDING_SIZE = 10 PREDICTION_ITERVAL bizim kaç 10 dakika sonraki hava ısısını tahmin edeceğimizi, WINDOW_SIZE son kaç 10 dakikalık ölçümlerden kestirim yapacağımızı, SLIDING_SIZE ise kaydırma miktarını belirtmektedir. Bu kaydırma miktarı 1 olarak alınırsa veri kümesi çok büyümektedir. Bu nedenle biz örneğimizde kaydırmayı 10'arlı yapacağız. Biizm öncelikle veriler üzerinde önişlemleri yapmamız gerekir. Veri kümesindeki tarih ve zaman bilgisi kategorik bir bilgi olarak ele alınabilir. Burada makul kategori sayısı ile bu sütunu sayısallaştırabiliriz: 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) from sklearn.preprocessing import OneHotEncoder ohe = OneHotEncoder(sparse_output=False) ohe.fit(df[['Month', 'Hour-Minute']]) ohe_result = ohe.transform(df[['Month', 'Hour-Minute']]) df = pd.concat([df, pd.DataFrame(ohe_result)], axis=1) df.drop(['Month', 'Hour-Minute'], axis=1, inplace=True) Burada önce tarih ve zaman sütununu parse ettik. Sonra ay bilgisini ve saat ile dakika bilgisini one-hot-encoding uyglayarak sayısallaştırdık. Bunun sonucunda aşağıdaki gibi x ve y veri kümelerini elde ettik: raw_dataset_x = df.to_numpy('float32') raw_dataset_y = df['T (degC)'].to_numpy('float32') Burada ölçeklemenin ne zaman yapılacağı konusunda iki seçenek olabilir. Birinci seçenek ölçeklemenin henüz eğitim verileri fit için uygun hale getirilmeden yapılması olabilir. İkinci seçenek ise eğitim verilerinin fit işlemi için uygun hale getirilmesimden sonra ölçeklemein yapılmasıdır. Ancak scikit-learn kütüphanesindeli ölçekleme sınıfları iki boyutlu veri kümsini girdi olarak almaktadır. Veriler üç boyutlu hale getirildikten sonra ölçekleme yapılacaksa bu durumda onların önce yeniden ikiboyuta indirgenip işlemden sonra yeniden üç boyuta yükseltilmesi gerekir. Biz burada önce ölçekleme uygulayıp sonra verileri fit işlemine uygun hale getireceğiz. Verileri eğitim ve test biçiminde ikiye ayırırken train_test_split fonksiyonunun shuffle parametresini False yapmayı unutmayınız. from sklearn.model_selection import train_test_split raw_training_dataset_x, raw_test_dataset_x, raw_training_dataset_y, raw_test_dataset_y = train_test_split(raw_dataset_x, raw_dataset_y, test_size=0.2, shuffle=False) Artık ölçekleme uygulayabiliriz: from sklearn.preprocessing import StandardScaler ss = StandardScaler() ss.fit(raw_training_dataset_x) raw_scaled_training_dataset_x = ss.transform(raw_training_dataset_x) raw_scaled_test_dataset_x = ss.transform(raw_test_dataset_x) Verilerin fit işlemi için üç boyutlu hale getirilmesi eğitim ve test veri kümelerinde ayrı ayrı yapılmalıdır. Bu nedenle bu işlemi bir fonksiyuna devrettik: def create_ts_dataset(dataset_x, dataset_y, pi, ws, ss): x = [] y = [] for i in range(0, len(dataset_x) - ws - pi, ss): x.append(dataset_x[i:i + ws]) y.append(dataset_y[i + ws + pi - 1]) return np.array(x), np.array(y) Artık eğitim ve test verilerini bu fonksiyon yoluyla üç boyutlu hale getirebiliriz: scaled_training_dataset_x, training_dataset_y = create_ts_dataset(raw_scaled_training_dataset_x, raw_training_dataset_y, PREDICTION_INTERVAL, WINDOW_SIZE, SLIDING_SIZE) scaled_test_dataset_x, test_dataset_y = create_ts_dataset(raw_scaled_test_dataset_x, raw_test_dataset_y, PREDICTION_INTERVAL, WINDOW_SIZE, SLIDING_SIZE) Şimdi modeli oluşturalım: from tensorflow.keras import Sequential from tensorflow.keras.layers import Input, Conv1D, MaxPooling1D, Reshape, Dense model = Sequential(name='Jena-Climate') model.add(Input((scaled_training_dataset_x.shape[1], scaled_training_dataset_x.shape[2]), name='Input')) 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='linear', name='Output')) model.summary() model.compile(optimizer='rmsprop', loss='mean_squared_error', metrics=['mean_absolute_error']) Eğitim şöyle yapılabilir: from tensorflow.keras.callbacks import EarlyStopping esc = EarlyStopping(monitor="val_loss", patience=5, restore_best_weights=True) hist = model.fit(scaled_training_dataset_x, training_dataset_y, epochs=100, batch_size=32, validation_split=0.2, callbacks=[esc]) Kesirim işlemi de şöyle yapılabilir: 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]}') Kestirim işleminde bir günlük veri (WINDOW_SIZE kadar veri) "predict.csv" dosyasında bulundurulmuştur. Bu dosyadan verileri okuyup yine aynı sayıllaştırmaları ve ölçeklemeyi uygulamamaız gereir: predict_df = pd.read_csv('predict.csv') predict_df['Month'] = predict_df['Date Time'].str[3:5] predict_df['Hour-Minute'] = predict_df['Date Time'].str[11:16] predict_df.drop(['Date Time'], axis=1, inplace=True) ohe_result = ohe.transform(predict_df[['Month', 'Hour-Minute']]) predict_df = pd.concat([predict_df, pd.DataFrame(ohe_result)], axis=1) predict_df.drop(['Month', 'Hour-Minute'], axis=1, inplace=True) predict_dataset = predict_df.to_numpy('float32') scaled_predict_dataset = ss.transform(predict_dataset) predict_result = model.predict(scaled_predict_dataset.reshape(1, predict_dataset.shape[0], predict_dataset.shape[1])) for presult in predict_result[:, 0]: print(presult) Aşağıda örnek bir bütün olarak verilmektedir. #---------------------------------------------------------------------------------------------------------------------------- import pandas as pd import numpy as np PREDICTION_INTERVAL = 24 * 60 // 10 # 144 WINDOW_SIZE = 24 * 60 // 10 # 144 SLIDING_SIZE = 5 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) from sklearn.preprocessing import OneHotEncoder ohe = OneHotEncoder(sparse_output=False) ohe.fit(df[['Month', 'Hour-Minute']]) ohe_result = ohe.transform(df[['Month', 'Hour-Minute']]) df = pd.concat([df, pd.DataFrame(ohe_result)], axis=1) df.drop(['Month', 'Hour-Minute'], axis=1, inplace=True) raw_dataset_x = df.to_numpy('float32') raw_dataset_y = df['T (degC)'].to_numpy('float32') from sklearn.model_selection import train_test_split raw_training_dataset_x, raw_test_dataset_x, raw_training_dataset_y, raw_test_dataset_y = train_test_split(raw_dataset_x, raw_dataset_y, test_size=0.2, shuffle=False) from sklearn.preprocessing import StandardScaler ss = StandardScaler() ss.fit(raw_training_dataset_x) raw_scaled_training_dataset_x = ss.transform(raw_training_dataset_x) raw_scaled_test_dataset_x = ss.transform(raw_test_dataset_x) def create_ts_dataset(dataset_x, dataset_y, pi, ws, ss): x = [] y = [] for i in range(0, len(dataset_x) - ws - pi, ss): x.append(dataset_x[i:i + ws]) y.append(dataset_y[i + ws + pi - 1]) return np.array(x), np.array(y) scaled_training_dataset_x, training_dataset_y = create_ts_dataset(raw_scaled_training_dataset_x, raw_training_dataset_y, PREDICTION_INTERVAL, WINDOW_SIZE, SLIDING_SIZE) scaled_test_dataset_x, test_dataset_y = create_ts_dataset(raw_scaled_test_dataset_x, raw_test_dataset_y, PREDICTION_INTERVAL, WINDOW_SIZE, SLIDING_SIZE) from tensorflow.keras import Sequential from tensorflow.keras.layers import Input, Conv1D, MaxPooling1D, Reshape, Dense model = Sequential(name='Jena-Climate') model.add(Input((scaled_training_dataset_x.shape[1], scaled_training_dataset_x.shape[2]), name='Input')) 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='linear', name='Output')) model.summary() model.compile(optimizer='rmsprop', loss='mean_squared_error', metrics=['mean_absolute_error']) from tensorflow.keras.callbacks import EarlyStopping esc = EarlyStopping(monitor="val_loss", patience=5, restore_best_weights=True) hist = model.fit(scaled_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 - Mean Squared Error', pad=10, fontsize=14) plt.xticks(range(0, 300, 10)) plt.plot(hist.epoch, hist.history['mean_absolute_error']) plt.plot(hist.epoch, hist.history['val_mean_absolute_error']) plt.legend(['MSE', 'Validation MSE']) plt.show() # evaluation 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]}') # prediction predict_df = pd.read_csv('predict.csv') predict_df['Month'] = predict_df['Date Time'].str[3:5] predict_df['Hour-Minute'] = predict_df['Date Time'].str[11:16] predict_df.drop(['Date Time'], axis=1, inplace=True) ohe_result = ohe.transform(predict_df[['Month', 'Hour-Minute']]) predict_df = pd.concat([predict_df, pd.DataFrame(ohe_result)], axis=1) predict_df.drop(['Month', 'Hour-Minute'], axis=1, inplace=True) predict_dataset = predict_df.to_numpy('float32') scaled_predict_dataset = ss.transform(predict_dataset) predict_result = model.predict(scaled_predict_dataset.reshape(1, predict_dataset.shape[0], predict_dataset.shape[1])) for presult in predict_result[:, 0]: print(presult) #---------------------------------------------------------------------------------------------------------------------------- Daha önceden de belirttiğimiz gibi bu tür yoğun verilerin kullanıldığı durumlarda eğer mümkünse eğitim, test ve kestirim işlemlerinin parçalı bir biçimde yapılması daha uygundur. Biz yukarıdaki örnekte tüm eğitim verilerini tek hamlede oluşturduk. Bu veriler de çok yer kaplıyordu. Şimdi aynı örneği daha önce görmüş olduğumuz parçalı eğitim tekniği ile gerçekleşirelim. Parçalı eğitimde dikkat edilecek anahtar noktalar şunlardır: - Bizin parçalı eğitim sınıfına (DataGenerator sınıfına) bazı bilgileri geçirmemiz gerekir. Sınıfın __init__ metodu şöyle olabilir: def __init__(self, raw_x, raw_y, batch_size, pi, ws, ss, *, shuffle=True): super().__init__() self.raw_x = raw_x self.raw_y = raw_y self.batch_size = batch_size self.pi = pi self.ws = ws self.ss = ss self.shuffle = shuffle self.nbatches = (len(raw_x) - pi - ws) // batch_size // ss self.index_list = list(range((len(raw_x) - pi - ws) // ss)) - Sınıfın __len__ metodu bir epoch'un kaç batch'ten oluşacağı bilgisiyle geri döndürülmelidir. Bu hesap şöyle yapılmıştır: self.nbatches = (len(raw_x) - pi - ws) // batch_size // ss - Sınıfın __getitem__ metodu model sınıfının fit, evaluate gibi metotları tarafından köşeli parantez içerisine batch numarası verilerek çağrılmaktadır. - Epoch'lar arasında hiç karıştırma yapmayabiliriz. Ancak eğer karıştırma yapacaksak asıl veri kümesini karıştırmak iyi bir fikir değildir. Biz örneğimizde bir batch'i oluşturacak olan her eleman için bir index numarası oluşturup bu index dizini karıştırdık. __getitem__ metodu şöyle yazılmıştır: def __getitem__(self, batch_no): x = np.zeros((self.batch_size, self.ws, self.raw_x.shape[1])) y = np.zeros(self.batch_size) for i in range(self.batch_size): offset = self.index_list[batch_no * self.batch_size + i] * self.ss x[i] = self.raw_x[offset:offset + self.ws] y[i] = self.raw_y[offset + self.ws + self.pi - 1] return tf.convert_to_tensor(x), tf.convert_to_tensor(y) Burada baştan x ve y için içi sıfırlarla dolu NumPy dizileri yaratılmıştır. Sonra batch'in uzunluğu kadar bir döngü oluşturulmuştur. Karıştırılmış index listesindeki ilgi yer batch_no * self.batch_size ile elde edilmektedir. Bu index'ten itibaren bu dizide self.batch_size kadar ilerlenip oradaki index'ler kullanılırsa aslında asıl dizinin farklı yerlerine erişilmiş olacaktır. Tabii diziden ilgili index çekildiğinde bunun asıl dizinin hangi offseti olacağı bu değerin self.ss ile çarpımıyla elde edilmiştir. - Her epoch bittiğinde çağrılan on_epoch_end işleminde karıştırma yapılmaktadır: def on_epoch_end(self): if self.shuffle: np.random.shuffle(self.index_list) Aşağıda örnek bir bütün olarak verilmiştir. #---------------------------------------------------------------------------------------------------------------------------- import pandas as pd import numpy as np PREDICTION_INTERVAL = 24 * 60 // 10 # 144 WINDOW_SIZE = 24 * 60 // 10 # 144 SLIDING_SIZE = 5 BATCH_SIZE = 32 EPOCHS = 200 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) from sklearn.preprocessing import OneHotEncoder ohe = OneHotEncoder(sparse_output=False) ohe.fit(df[['Month', 'Hour-Minute']]) ohe_result = ohe.transform(df[['Month', 'Hour-Minute']]) df = pd.concat([df, pd.DataFrame(ohe_result)], axis=1) df.drop(['Month', 'Hour-Minute'], axis=1, inplace=True) raw_dataset_x = df.to_numpy('float32') raw_dataset_y = df['T (degC)'].to_numpy('float32') from sklearn.model_selection import train_test_split raw_temp_dataset_x, raw_test_dataset_x, raw_temp_dataset_y, raw_test_dataset_y = train_test_split(raw_dataset_x, raw_dataset_y, test_size=0.1, shuffle=False) raw_training_dataset_x, raw_validation_dataset_x, raw_training_dataset_y, raw_validation_dataset_y = \ train_test_split(raw_temp_dataset_x, raw_temp_dataset_y, test_size=0.1, shuffle=False) from sklearn.preprocessing import StandardScaler ss = StandardScaler() ss.fit(raw_training_dataset_x) raw_scaled_training_dataset_x = ss.transform(raw_training_dataset_x) raw_scaled_validation_dataset_x = ss.transform(raw_validation_dataset_x) raw_scaled_test_dataset_x = ss.transform(raw_test_dataset_x) import tensorflow as tf from tensorflow.keras.utils import PyDataset class DataGenerator(PyDataset): def __init__(self, raw_x, raw_y, batch_size, pi, ws, ss, *, shuffle=True): super().__init__() self.raw_x = raw_x self.raw_y = raw_y self.batch_size = batch_size self.pi = pi self.ws = ws self.ss = ss self.shuffle = shuffle self.nbatches = (len(raw_x) - pi - ws) // batch_size // ss self.index_list = list(range((len(raw_x) - pi - ws) // ss)) def __len__(self): return self.nbatches def __getitem__(self, batch_no): x = np.zeros((self.batch_size, self.ws, self.raw_x.shape[1])) y = np.zeros(self.batch_size) for i in range(self.batch_size): offset = self.index_list[batch_no * self.batch_size + i] * self.ss x[i] = self.raw_x[offset:offset + self.ws] y[i] = self.raw_y[offset + self.ws + self.pi - 1] return tf.convert_to_tensor(x), tf.convert_to_tensor(y) def on_epoch_end(self): if self.shuffle: np.random.shuffle(self.index_list) from tensorflow.keras import Sequential from tensorflow.keras.layers import Input, Conv1D, MaxPooling1D, Reshape, Dense model = Sequential(name='Jena-Climate') model.add(Input((WINDOW_SIZE, raw_training_dataset_x.shape[1]), name='Input')) 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='linear', name='Output')) model.summary() model.compile(optimizer='rmsprop', loss='mean_squared_error', metrics=['mean_absolute_error']) from tensorflow.keras.callbacks import EarlyStopping esc = EarlyStopping(monitor="val_loss", patience=10, restore_best_weights=True) dg_training = DataGenerator(raw_scaled_training_dataset_x, raw_training_dataset_y, BATCH_SIZE, PREDICTION_INTERVAL, WINDOW_SIZE, SLIDING_SIZE, shuffle=False) dg_validation = DataGenerator(raw_scaled_validation_dataset_x, raw_validation_dataset_y, BATCH_SIZE, PREDICTION_INTERVAL, WINDOW_SIZE, SLIDING_SIZE, shuffle=False) hist = model.fit(dg_training, validation_data = dg_validation, epochs=EPOCHS, verbose=1, 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 - Mean Squared Error', pad=10, fontsize=14) plt.xticks(range(0, 300, 10)) plt.plot(hist.epoch, hist.history['mean_absolute_error']) plt.plot(hist.epoch, hist.history['val_mean_absolute_error']) plt.legend(['MSE', 'Validation MSE']) plt.show() # evaluation dg_test = DataGenerator(raw_scaled_test_dataset_x, raw_test_dataset_y, BATCH_SIZE, PREDICTION_INTERVAL, WINDOW_SIZE, SLIDING_SIZE, shuffle=False) eval_result = model.evaluate(dg_test) for i in range(len(eval_result)): print(f'{model.metrics_names[i]}: {eval_result[i]}') # prediction predict_df = pd.read_csv('predict.csv') predict_df['Month'] = predict_df['Date Time'].str[3:5] predict_df['Hour-Minute'] = predict_df['Date Time'].str[11:16] predict_df.drop(['Date Time'], axis=1, inplace=True) ohe_result = ohe.transform(predict_df[['Month', 'Hour-Minute']]) predict_df = pd.concat([predict_df, pd.DataFrame(ohe_result)], axis=1) predict_df.drop(['Month', 'Hour-Minute'], axis=1, inplace=True) predict_dataset = predict_df.to_numpy('float32') scaled_predict_dataset = ss.transform(predict_dataset) predict_result = model.predict(scaled_predict_dataset.reshape(1, predict_dataset.shape[0], predict_dataset.shape[1])) for presult in predict_result[:, 0]: print(presult) #---------------------------------------------------------------------------------------------------------------------------- Biz yazısal örneklerde ve zaman serilerinde tek boyutlu evrişim işlemlerini gördük. Evrişim işlemi resimsel uygulamalarda pixel'leri birbirleriyle ilişkilendirmek için en önemli ve etkin işlemlerden biridir. Ancak metinsel uygulamalarda ve zaman serilerinde evrişim işlemi bazı nedenlerden dolayı önemli faydalar sağlayamaktadır. Anımsanacağı gibi bir evrişim işleminde birbirine yakın öğeleri ilişkilendirmeye çalışıyorduk. Sonra yeniden evrişim işlemleriyle bunu daha büyük öeğelere yaydırmaya çalışıyorduk. Ancak evrişim işlemi metinsel uygulamalarda ve zaman serilerinde iyi bir bağlamsal etki oluşturamamaktadır. Bu tür uygulamalarda ağa hafıza kazandırmak gerekir. İşte ağa hafıza kazandırmak için "geri beslemeli ağlardan (recurrent neural networks)" faydalanılmaktadır. Geri beslemeli ağlarda temel fikir çıktının bir biçimde girdi ile ilişkilendirilip unutulmamasının sağlanmasıdır. Eğitim sırasında bir önceki çıktı bir sonraki girdi ile kombine edilerek ağa verilmektedir. Örneğin ağında aşağıdaki girdileri teket teker aldığını varsayalım: xxxxxxxxxxxx yyyyyyyyyyyy zzzzzzzzzzzz kkkkkkkkkkkk Biz önce katmana x'lerin bulunduğu satırı, sonra y'lerin bulunduğu satırı ve sonra da sırasıyla diğerlerini girdi olarak verdiğimizi düşünelim. İşte katmana x'lerin bulunduğu satırı verdiğimizde katmandan elde edeceğimiz çıktı değerini y'lerin bulunduğu satırı verirken kombine ederek veririz. y'lerden elde edilen çıktıyı da z'leri katmana verirken kombine ederiz. Yani her çıktıyı bir sonraki girdi ile kombine ederek ağa verebiliriz. Peki bu bize ne sağlayacaktır? İşte bu sayede ağın eski bilgileri unutmaması onlardan elde edilen ana fikrin sürekli taze tutması sağlanmaktadır. Aslında bu yöntem insanın hafıza sistemine de benzemektedir. Biz bir bilgiyi kalıcı hale getirmek için sürekli tekrarlarız. Tekrarlanmayan bilgi kısa süreli hafızadan (short term memory) uçup gitmektedir. Pekiyi bu geri besleme fikri bu haliyle ağa hafıza kazandırmakta yeterli olmakta mıdır? Geri beslemeli ağlar bu anlamda ağa hafıza kazandırmaya önemli bir katkı sağlamaktadır. Ancak bu haliyle ağ eski bilgileri uzun süre hafızasında tutamamaktadır. Bu probleme genel olarak "gradyan kaybolması problemi (vanishing gradient problem)" denilmektedir. Son on senedir bu problem üzerinde çokça çalışılmış ve geri beslemeli ağlar bu problemi tam olarak ortadan kaldırmasa da azaltacak biçimde evrimleşmiştir. Gradyan kaybolması (vanishing gradient) hakkında ileride bilgiler verilecektir. #---------------------------------------------------------------------------------------------------------------------------- #---------------------------------------------------------------------------------------------------------------------------- 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 satır satır verilir. Girdinin her satırından bir çıktı elde edilir. Sonra bu çıktı girdinin sonraki satırı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 önceleri yaptığımız gibi Dense katmanlara verilir. Yani geri besleme katmanı genellikle derin ağlardaki ilk katmanları oluşturmaktadır. Bir Dense katmandaki bir nöronu düşünelim. Bu nörona o katmanın girdisi kadar giriş uygulanmaktadır. Aynı zamanda bu nöronda bir "bias" değeri de vardır. Anımsanacağı gibi bu nöronun çıktısı şöyle oluşturulmaktadır: activation(dot(W, X) + b) ---> çıktı Bizim bu nöronda konumlandırmaya çalıştığımız değerler W ve b değerleridir. Örneğin 5 girdiye sahip olan bir Dense katmandaki nörounun çıktısı şöyle hesaplanmaktadır: activation(w0x0 + w1x1 + w2x2 + w3x3 + w4x4 + b) ---> çıktı Bu nöronda toplam 6 tane eğitilebilir parametre olduğuna dikkat ediniz. Eğer Dense katmanda bunun gibi N tane nöron varsa bu durumda eğitilebilir parametrelerin sayısı N * 6 olacaktır. Katmanın girdi nöronlarının sayısı K tane olmak üzere Dense katmandaki eğitilebilir parametrelerin sayısının K * N + N olduğunu anımsayınız. Şimdi bir zaman serisi veri kümesindeki satırları geri besleme (recurrent) katmanına uygulayalım. Yine katmandaki belli bir nörounu dikkate alalım. Katmanda toplam 10 tane nöron olsun. Katmanın girişi de (özelliklerin sayısı) 5 tane olsun. İşte geri besleme katmanında her yeni girdi değerleri bu nörona aynı zamanda bir önceki çıktı değerleriyle birlikte verilir. Yani nöronun girdileri yalnızca gerçek girdilerden değil aynı zamanda bir önceki çıktılardan da oluşmaktadır. Bu örneğimizde nöronun toplam 5 tane girdisi vardır. Katmanda da 10 nöron vardır. O halde katmandaki bir önceki çıktı da 10 tane olacaktır. İşte yeni girdi (sıradaki satır) ile katmanın bir önceki çıktı nöronlarının değeri bu nörona girdi yapılmaktadır. Bu durumda katmanın girdisi K tane nörondan oluşuyorsa ve katmanda N tane nöron varsa bu nöronun girdisi K + N tane olacaktır. Tabii bir de "bias" değeri vardır. Bu durumda bu nörondaki eğitilebilir parametrelerin sayısı K + N + 1 tane olacaktır. Örneğimizde nöroa 5 + 10 = 15 giriş uygulanacaktır. Bu 15 giriş ağırlık değerleriyle dot product yapılacak ve buna bir de "bias" değeri toplanacaktır. Sonra da elde edilen bu değere aktivasyon fonksiyonu uygulanacaktır. Geri beslemeli ağlardaki katmanlarda bulunan nöronların çıktılarını aşağıdaki gibi formülüze edebiliri: ht = activation(dot(W, xt) + dot(U, ht-1) + b) Burada ht nöronun çıktısını, xt uygulanan girdiyi belirtmektedir. ht-1 ise katmanın bir önceki çıktısını temsil etmektedir. W değeri girdiler için konumlandırılacak ağırlık değerlerini U ise geri besleme için konumlandırılacak ağırlık değerlerini belirtmektedir. Geri besleme katmanı Keras'ta SimpleRNN isimli katman sınıfıyla temsil edilmektedir. Bu katman değerleri satır satır ele alıp yukarıda belirttiğimiz gibi bir işlem yapmaktadır. Pekiyi geri besleme katmanındaki toplam eğitilebilir parametrelerin sayısı nasıl hesaplanmaktadır? Bu katmana K tane girdi uygulandığını katmanda da N tane nöron olduğunu düşünelim. Bu durumda katmandaki her nöronda yukarıda belirttiğimiz gibi K + N + 1 tane eğitilebilir parametre bulunacaktır. Bu nörondan toplam N tane olduğunda göre katmandaki toplam eğitilebilir parametrelerin sayısı (K + N + 1) * N tane olacaktı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, seed=None, **kwargs ) Metodun ilk parametresi katmandaki nöron sayısını belirtmektedir. Aktivasyon fonksiyonunun default olarak 'tanh' biçiminde alındığına dikkat ediniz. Geri besleme katmanlarında ReLU fonksiyonu yerine tanh (hiperbolik tanjant) fonksiyonu tercih edilmektedir. Bu fonksiyon bu katmanda "gradyen azalması (vanishing gradient)" problemine nispeten bir direnç oluşturmaktadır. from tensorflow.keras import Sequential from tensorflow.keras.layers import Input, SimpleRNN, Reshape, Dense model = Sequential(name='SimpleRNN-Test') model.add(Input((100, 10), name='Input')) model.add(SimpleRNN(128, activation='tanh', name='SimpleRNN')) 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='linear', name='Output')) model.summary() Bu modeldeki özet bilgi şöyle elde edilmiştir: Model: "SimpleRNN-Test" ┌─────────────────────────────────┬────────────────────────┬───────────────┐ │ Layer (type) │ Output Shape │ Param # │ ├─────────────────────────────────┼────────────────────────┼───────────────┤ │ SimpleRNN (SimpleRNN) │ (None, 128) │ 17,792 │ ├─────────────────────────────────┼────────────────────────┼───────────────┤ │ Reshape (Reshape) │ (None, 128) │ 0 │ ├─────────────────────────────────┼────────────────────────┼───────────────┤ │ Hidden-1 (Dense) │ (None, 256) │ 33,024 │ ├─────────────────────────────────┼────────────────────────┼───────────────┤ │ Hidden-2 (Dense) │ (None, 256) │ 65,792 │ ├─────────────────────────────────┼────────────────────────┼───────────────┤ │ Output (Dense) │ (None, 1) │ 257 │ └─────────────────────────────────┴────────────────────────┴───────────────┘ Total params: 116,865 (456.50 KB) Trainable params: 116,865 (456.50 KB) Non-trainable params: 0 (0.00 B) Biz Dense katmanlardaki eğitilebilir parametrelerin nasıl hesaplandığını zaten görmüştük. SimpleRNN katmanınında girdi olarak 10 nöron uygulanmaktadır. Katmanda toplam 128 nöron vardır. Bu durumda katmandaki her nörona aslında 10 + 128 girdi uygulanmaktadır. Bir nörondaki toplam eğitilebilir parametrelerin sayısı 10 + 128 + 1 tane olacaktır. Katmanda toplam 128 nöron olduğuna göre eğitilebilir parametrelerin toplam sayısı 128 * (10 + 128 + 1) = 17792 tanedir. #---------------------------------------------------------------------------------------------------------------------------- from tensorflow.keras import Sequential from tensorflow.keras.layers import Input, SimpleRNN, Reshape, Dense model = Sequential(name='SimpleRNN-Test') model.add(Input((100, 10), name='Input')) model.add(SimpleRNN(128, activation='tanh', name='SimpleRNN')) 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='linear', name='Output')) model.summary() #---------------------------------------------------------------------------------------------------------------------------- Aşağıdaki örnekte SimpleRNN katmanının bir simülasyonu yapılmıştır. Bu örnekte girdiler satırlardan oluşmaktadır. Her satır bir önceki çıktı ile dot-product yapılmaktadır. Ayrıca örnekte her satır için elde edilen çıktıların biriktirildiğini de görüyorsunuz. Bu biriktirme hakkında izleyen paragrafta açıklamalar yapacağız. #---------------------------------------------------------------------------------------------------------------------------- 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 katmana 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. SimpleRNN katmanının parametrik yapısına bir daha dikkat ediniz: 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 parametresi geri besleme katmanındaki nöron sayısını belirtmektedir. Geri besleme katmanlarının default aktivasyon fonksiyonun "tanh" biçiminde olduğunu belirtmiştik. 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 (satırların) çı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ı zamansal veriyi belirtmektedir. Keras bu durumda bu matrisin her bir satırını zamansal veri biçiminde ele alır ve önceki çıktıyla işleme sokar. (Tabii Keras paralel programlama teknikleri ile daha karmaşık bir gerçekleştirime sahiptir. Ancak SimpleRNN katmanı satırları tek tek ele alıp kendi içerisinde yukarıda belirttiğimiz gibi işleme sokmaktadır.) Şimdi SimpleRNN katmanındaki return_sequences parametresinin True geçilmesi durumundaki çıktının nasıl olacağı üzerinde duralım. Normalde bu katmanın çıktısı katmandaki nöron sayısı kadardır. Ancak return_sequences parametresi True yapıldığı zaman her bir satırın çıktısı biriktirilecektir. Katmandaki nöron sayısı N ve zamansal verilerdeki satır sayısı R olmak üzere çıktı da RxN'lik bir matris haline gelecektir. Şimdi de daha önce yapmış olduğumuz örnek modeldeki return_sequences parametresini True yaparak elde edilen modelin örnek çıktısını inceleyelim. Anımsanacağı gibi bu modelde her satırda toplam 10 tane özellik, SimpleRNN kkatmanında da 128 nöron bulunuyordu: from tensorflow.keras import Sequential from tensorflow.keras.layers import Input, SimpleRNN, Reshape, Dense model = Sequential(name='SimpleRNN-Test') model.add(Input((100, 10), name='Input')) model.add(SimpleRNN(128, activation='tanh', return_sequences=True, name='SimpleRNN')) 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='linear', name='Output')) model.summary() Modelin özet çıktısı şöyledir: Model: "SimpleRNN-Test" ┌─────────────────────────────────┬────────────────────────┬───────────────┐ │ Layer (type) │ Output Shape │ Param # │ ├─────────────────────────────────┼────────────────────────┼───────────────┤ │ SimpleRNN (SimpleRNN) │ (None, 100, 128) │ 17,792 │ ├─────────────────────────────────┼────────────────────────┼───────────────┤ │ Reshape (Reshape) │ (None, 12800) │ 0 │ ├─────────────────────────────────┼────────────────────────┼───────────────┤ │ Hidden-1 (Dense) │ (None, 256) │ 3,277,056 │ ├─────────────────────────────────┼────────────────────────┼───────────────┤ │ Hidden-2 (Dense) │ (None, 256) │ 65,792 │ ├─────────────────────────────────┼────────────────────────┼───────────────┤ │ Output (Dense) │ (None, 1) │ 257 │ └─────────────────────────────────┴────────────────────────┴───────────────┘ Total params: 3,360,897 (12.82 MB) Trainable params: 3,360,897 (12.82 MB) Non-trainable params: 0 (0.00 B) Burada her veri 100 satır 10 sütundan oluşmaktadır. Bu 100 satır SimpleRNN katmanına yukarıda açıkladığımız gibi peşi sıra uygulanacaktır. Ancak toplamda bu 100 satır uygulandığında tek bir çıkış elde edilmeyecek bu çıkışlar biriktirilecektir. Bu durumda SimpleRNN katmanının çıktısı 128 değil 100 * 128 = 12800 tane olacaktır. Biz bu matrisi reshape yaparak sonraki Dense katmana uygulayacağız. Örneğimizde birinci saklı katmandaki eğitilebilir parametrelerin sayısının da arttığını görüyorsunuz. Bu katmandaki eğitilebilir parametrelerin sayısı artık 12800 * 256 + 256 = 3277056 tane olacaktır. #---------------------------------------------------------------------------------------------------------------------------- #---------------------------------------------------------------------------------------------------------------------------- Şimdi Jena Climate örneğini SimpleRNN katmanını kullanarak yeniden yapalım. Modelimiz şöyle olabilir: from tensorflow.keras import Sequential from tensorflow.keras.layers import Input, SimpleRNN, Reshape, Dense model = Sequential(name='Jena-Climate') model.add(Input((WINDOW_SIZE, raw_training_dataset_x.shape[1]), name='Input')) model.add(SimpleRNN(128, activation='tanh', name='SimpleRNN')) 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='linear', name='Output')) model.summary() Daha önce yapmış olduğumuz modeldeki Conv1D ve MaxPooling1D katmanlarını kaldırarak onların yerine SimpleRNN katmanını yerleştirdik. Modlein özet çıktısı da şöyledir: Model: "Jena-Climate" ┌─────────────────────────────────┬────────────────────────┬───────────────┐ │ Layer (type) │ Output Shape │ Param # │ ├─────────────────────────────────┼────────────────────────┼───────────────┤ │ SimpleRNN (SimpleRNN) │ (None, 128) │ 38,272 │ ├─────────────────────────────────┼────────────────────────┼───────────────┤ │ Reshape (Reshape) │ (None, 128) │ 0 │ ├─────────────────────────────────┼────────────────────────┼───────────────┤ │ Hidden-1 (Dense) │ (None, 256) │ 33,024 │ ├─────────────────────────────────┼────────────────────────┼───────────────┤ │ Hidden-2 (Dense) │ (None, 256) │ 65,792 │ ├─────────────────────────────────┼────────────────────────┼───────────────┤ │ Output (Dense) │ (None, 1) │ 257 │ └─────────────────────────────────┴────────────────────────┴───────────────┘ Total params: 137,345 (536.50 KB) Trainable params: 137,345 (536.50 KB) Non-trainable params: 0 (0.00 B) Aşağıda örneği bir bütün olarak veriyoruz. #---------------------------------------------------------------------------------------------------------------------------- import pandas as pd import numpy as np PREDICTION_INTERVAL = 24 * 60 // 10 # 144 WINDOW_SIZE = 24 * 60 // 10 # 144 SLIDING_SIZE = 5 BATCH_SIZE = 32 EPOCHS = 200 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) from sklearn.preprocessing import OneHotEncoder ohe = OneHotEncoder(sparse_output=False) ohe.fit(df[['Month', 'Hour-Minute']]) ohe_result = ohe.transform(df[['Month', 'Hour-Minute']]) df = pd.concat([df, pd.DataFrame(ohe_result)], axis=1) df.drop(['Month', 'Hour-Minute'], axis=1, inplace=True) raw_dataset_x = df.to_numpy('float32') raw_dataset_y = df['T (degC)'].to_numpy('float32') from sklearn.model_selection import train_test_split raw_temp_dataset_x, raw_test_dataset_x, raw_temp_dataset_y, raw_test_dataset_y = train_test_split(raw_dataset_x, raw_dataset_y, test_size=0.1, shuffle=False) raw_training_dataset_x, raw_validation_dataset_x, raw_training_dataset_y, raw_validation_dataset_y = train_test_split(raw_temp_dataset_x, raw_temp_dataset_y, test_size=0.1, shuffle=False) from sklearn.preprocessing import StandardScaler ss = StandardScaler() ss.fit(raw_training_dataset_x) raw_scaled_training_dataset_x = ss.transform(raw_training_dataset_x) raw_scaled_validation_dataset_x = ss.transform(raw_validation_dataset_x) raw_scaled_test_dataset_x = ss.transform(raw_test_dataset_x) import tensorflow as tf from tensorflow.keras.utils import PyDataset class DataGenerator(PyDataset): def __init__(self, raw_x, raw_y, batch_size, pi, ws, ss, *, shuffle=True): super().__init__() self.raw_x = raw_x self.raw_y = raw_y self.batch_size = batch_size self.pi = pi self.ws = ws self.ss = ss self.shuffle = shuffle self.nbatches = (len(raw_x) - pi - ws) // batch_size // ss self.index_list = list(range((len(raw_x) - pi - ws) // ss)) def __len__(self): return self.nbatches def __getitem__(self, batch_no): x = np.zeros((self.batch_size, self.ws, self.raw_x.shape[1])) y = np.zeros(self.batch_size) for i in range(self.batch_size): offset = self.index_list[batch_no * self.batch_size + i] * self.ss x[i] = self.raw_x[offset:offset + self.ws] y[i] = self.raw_y[offset + self.ws + self.pi - 1] return tf.convert_to_tensor(x), tf.convert_to_tensor(y) def on_epoch_end(self): if self.shuffle: np.random.shuffle(self.index_list) from tensorflow.keras import Sequential from tensorflow.keras.layers import Input, SimpleRNN, Reshape, Dense model = Sequential(name='Jena-Climate') model.add(Input((WINDOW_SIZE, raw_training_dataset_x.shape[1]), name='Input')) model.add(SimpleRNN(128, activation='tanh', name='SimpleRNN')) 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='linear', name='Output')) model.summary() model.compile(optimizer='rmsprop', loss='mean_squared_error', metrics=['mean_absolute_error']) from tensorflow.keras.callbacks import EarlyStopping esc = EarlyStopping(monitor="val_loss", patience=10, restore_best_weights=True) dg_training = DataGenerator(raw_scaled_training_dataset_x, raw_training_dataset_y, BATCH_SIZE, PREDICTION_INTERVAL, WINDOW_SIZE, SLIDING_SIZE, shuffle=False) dg_validation = DataGenerator(raw_scaled_validation_dataset_x, raw_validation_dataset_y, BATCH_SIZE, PREDICTION_INTERVAL, WINDOW_SIZE, SLIDING_SIZE, shuffle=False) hist = model.fit(dg_training, validation_data = dg_validation, epochs=EPOCHS, verbose=1, 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 - Mean Squared Error', pad=10, fontsize=14) plt.xticks(range(0, 300, 10)) plt.plot(hist.epoch, hist.history['mean_absolute_error']) plt.plot(hist.epoch, hist.history['val_mean_absolute_error']) plt.legend(['MSE', 'Validation MSE']) plt.show() # evaluation dg_test = DataGenerator(raw_scaled_test_dataset_x, raw_test_dataset_y, BATCH_SIZE, PREDICTION_INTERVAL, WINDOW_SIZE, SLIDING_SIZE, shuffle=False) eval_result = model.evaluate(dg_test) for i in range(len(eval_result)): print(f'{model.metrics_names[i]}: {eval_result[i]}') # prediction predict_df = pd.read_csv('predict.csv') predict_df['Month'] = predict_df['Date Time'].str[3:5] predict_df['Hour-Minute'] = predict_df['Date Time'].str[11:16] predict_df.drop(['Date Time'], axis=1, inplace=True) ohe_result = ohe.transform(predict_df[['Month', 'Hour-Minute']]) predict_df = pd.concat([predict_df, pd.DataFrame(ohe_result)], axis=1) predict_df.drop(['Month', 'Hour-Minute'], axis=1, inplace=True) predict_dataset = predict_df.to_numpy('float32') scaled_predict_dataset = ss.transform(predict_dataset) predict_result = model.predict(scaled_predict_dataset.reshape(1, predict_dataset.shape[0], predict_dataset.shape[1])) for presult in predict_result[:, 0]: print(presult) #---------------------------------------------------------------------------------------------------------------------------- 73. Ders - 20/10/2024 - Pazar #---------------------------------------------------------------------------------------------------------------------------- #---------------------------------------------------------------------------------------------------------------------------- Şimdi de aynı örneği return_sequences parametresini True geçerek yapalım: from tensorflow.keras import Sequential from tensorflow.keras.layers import Input, SimpleRNN, Reshape, Dense model = Sequential(name='Jena-Climate') model.add(Input((WINDOW_SIZE, raw_training_dataset_x.shape[1]), name='Input')) model.add(SimpleRNN(128, activation='tanh', return_sequences=True, name='SimpleRNN')) 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='linear', name='Output')) model.summary() Modelin özet çıktısı şöyledir: Aşağıdaki örnek yukarıdaki örneğin return_sequences=True dışında tamamen aynısıdır. #---------------------------------------------------------------------------------------------------------------------------- import pandas as pd import numpy as np PREDICTION_INTERVAL = 24 * 60 // 10 # 144 WINDOW_SIZE = 24 * 60 // 10 # 144 SLIDING_SIZE = 5 BATCH_SIZE = 32 EPOCHS = 200 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) from sklearn.preprocessing import OneHotEncoder ohe = OneHotEncoder(sparse_output=False) ohe.fit(df[['Month', 'Hour-Minute']]) ohe_result = ohe.transform(df[['Month', 'Hour-Minute']]) df = pd.concat([df, pd.DataFrame(ohe_result)], axis=1) df.drop(['Month', 'Hour-Minute'], axis=1, inplace=True) raw_dataset_x = df.to_numpy('float32') raw_dataset_y = df['T (degC)'].to_numpy('float32') from sklearn.model_selection import train_test_split raw_temp_dataset_x, raw_test_dataset_x, raw_temp_dataset_y, raw_test_dataset_y = train_test_split(raw_dataset_x, raw_dataset_y, test_size=0.1, shuffle=False) raw_training_dataset_x, raw_validation_dataset_x, raw_training_dataset_y, raw_validation_dataset_y = \ train_test_split(raw_temp_dataset_x, raw_temp_dataset_y, test_size=0.1, shuffle=False) from sklearn.preprocessing import StandardScaler ss = StandardScaler() ss.fit(raw_training_dataset_x) raw_scaled_training_dataset_x = ss.transform(raw_training_dataset_x) raw_scaled_validation_dataset_x = ss.transform(raw_validation_dataset_x) raw_scaled_test_dataset_x = ss.transform(raw_test_dataset_x) import tensorflow as tf from tensorflow.keras.utils import PyDataset class DataGenerator(PyDataset): def __init__(self, raw_x, raw_y, batch_size, pi, ws, ss, *, shuffle=True): super().__init__() self.raw_x = raw_x self.raw_y = raw_y self.batch_size = batch_size self.pi = pi self.ws = ws self.ss = ss self.shuffle = shuffle self.nbatches = (len(raw_x) - pi - ws) // batch_size // ss self.index_list = list(range((len(raw_x) - pi - ws) // ss)) def __len__(self): return self.nbatches def __getitem__(self, batch_no): x = np.zeros((self.batch_size, self.ws, self.raw_x.shape[1])) y = np.zeros(self.batch_size) for i in range(self.batch_size): offset = self.index_list[batch_no * self.batch_size + i] * self.ss x[i] = self.raw_x[offset:offset + self.ws] y[i] = self.raw_y[offset + self.ws + self.pi - 1] return tf.convert_to_tensor(x), tf.convert_to_tensor(y) def on_epoch_end(self): if self.shuffle: np.random.shuffle(self.index_list) from tensorflow.keras import Sequential from tensorflow.keras.layers import Input, SimpleRNN, Reshape, Dense model = Sequential(name='Jena-Climate') model.add(Input((WINDOW_SIZE, raw_training_dataset_x.shape[1]), name='Input')) model.add(SimpleRNN(128, activation='tanh', return_sequences=True, name='SimpleRNN')) 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='linear', name='Output')) model.summary() model.compile(optimizer='rmsprop', loss='mean_squared_error', metrics=['mean_absolute_error']) from tensorflow.keras.callbacks import EarlyStopping esc = EarlyStopping(monitor="val_loss", patience=10, restore_best_weights=True) dg_training = DataGenerator(raw_scaled_training_dataset_x, raw_training_dataset_y, BATCH_SIZE, PREDICTION_INTERVAL, WINDOW_SIZE, SLIDING_SIZE, shuffle=False) dg_validation = DataGenerator(raw_scaled_validation_dataset_x, raw_validation_dataset_y, BATCH_SIZE, PREDICTION_INTERVAL, WINDOW_SIZE, SLIDING_SIZE, shuffle=False) hist = model.fit(dg_training, validation_data = dg_validation, epochs=EPOCHS, verbose=1, 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 - Mean Squared Error', pad=10, fontsize=14) plt.xticks(range(0, 300, 10)) plt.plot(hist.epoch, hist.history['mean_absolute_error']) plt.plot(hist.epoch, hist.history['val_mean_absolute_error']) plt.legend(['MSE', 'Validation MSE']) plt.show() # evaluation dg_test = DataGenerator(raw_scaled_test_dataset_x, raw_test_dataset_y, BATCH_SIZE, PREDICTION_INTERVAL, WINDOW_SIZE, SLIDING_SIZE, shuffle=False) eval_result = model.evaluate(dg_test) for i in range(len(eval_result)): print(f'{model.metrics_names[i]}: {eval_result[i]}') # prediction predict_df = pd.read_csv('predict.csv') predict_df['Month'] = predict_df['Date Time'].str[3:5] predict_df['Hour-Minute'] = predict_df['Date Time'].str[11:16] predict_df.drop(['Date Time'], axis=1, inplace=True) ohe_result = ohe.transform(predict_df[['Month', 'Hour-Minute']]) predict_df = pd.concat([predict_df, pd.DataFrame(ohe_result)], axis=1) predict_df.drop(['Month', 'Hour-Minute'], axis=1, inplace=True) predict_dataset = predict_df.to_numpy('float32') scaled_predict_dataset = ss.transform(predict_dataset) predict_result = model.predict(scaled_predict_dataset.reshape(1, predict_dataset.shape[0], predict_dataset.shape[1])) for presult in predict_result[:, 0]: print(presult) #---------------------------------------------------------------------------------------------------------------------------- Aslında geri besleme katmanları genellikle bir kez değil üst üste birkaç kez uygulanmaktadır. Tıpkı üst üste evrişim uygulamak gibi üst üste geri besleme uygulamak hafızanın güçlendirilmesine fayda sağlamaktadır. Tabii SimpleRNN katmanını birden fazla kez uygulayacaksak bir önceki katmanın çıktısının bir matris olması gerekir. Bu da önceki SimpleRNN katmanının return_sequences parametresinin True geçilmesiyle sağlanabilir. Aşağıda birden fazla kez SimpleRNN katmanının kullanıldığı bir model örneğini görüyorsunuz: from tensorflow.keras import Sequential from tensorflow.keras.layers import Input, SimpleRNN, Reshape, Dense model = Sequential(name='Jena-Climate') model.add(Input((WINDOW_SIZE, raw_training_dataset_x.shape[1]), name='Input')) model.add(SimpleRNN(128, activation='tanh', return_sequences=True, name='SimpleRNN-1')) model.add(SimpleRNN(128, activation='tanh', return_sequences=True, name='SimpleRNN-2')) model.add(SimpleRNN(128, activation='tanh', return_sequences=True, name='SimpleRNN-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='linear', name='Output')) model.summary() Burada birinci SimpleRNN katmanın çıktısı artık bir matristir. Bu matris ikinci SimpleRNN katmanına sanki zaman serisi gibi uygulanmıştır. İkinci SimpleRNN katmanının çıktısı da üçüncü SimpleRNN katmanına zaman serisi biçiminde uygulanmıştır. Buradaki SimpleRNN katmanlarının return_sequences parametrelerinin True geçildiğine dikkat ediniz. Üst üste birden fazla kez geri besleme katmanı kullanıldığında modeldeki eğitilebilir parametrelerin sayısı artmaktadır. Eğitilebilir parametrelerin sayısının artması "overfitting" ya da "underfitting" oluşturabilmektedir. Yukarıdaki modelin özet çıktısı şöyledir: Model: "Jena-Climate" ┌─────────────────────────────────┬────────────────────────┬───────────────┐ │ Layer (type) │ Output Shape │ Param # │ ├─────────────────────────────────┼────────────────────────┼───────────────┤ │ SimpleRNN-1 (SimpleRNN) │ (None, 144, 128) │ 38,272 │ ├─────────────────────────────────┼────────────────────────┼───────────────┤ │ SimpleRNN-2 (SimpleRNN) │ (None, 144, 128) │ 32,896 │ ├─────────────────────────────────┼────────────────────────┼───────────────┤ │ SimpleRNN-3 (SimpleRNN) │ (None, 144, 128) │ 32,896 │ ├─────────────────────────────────┼────────────────────────┼───────────────┤ │ Reshape (Reshape) │ (None, 18432) │ 0 │ ├─────────────────────────────────┼────────────────────────┼───────────────┤ │ Hidden-1 (Dense) │ (None, 256) │ 4,718,848 │ ├─────────────────────────────────┼────────────────────────┼───────────────┤ │ Hidden-2 (Dense) │ (None, 256) │ 65,792 │ ├─────────────────────────────────┼────────────────────────┼───────────────┤ │ Output (Dense) │ (None, 1) │ 257 │ └─────────────────────────────────┴────────────────────────┴───────────────┘ Total params: 4,888,961 (18.65 MB) Trainable params: 4,888,961 (18.65 MB) Non-trainable params: 0 (0.00 B) Burada toplam eğitilebilir parametrelerin sayısının 5 milyona yakın olduğu görülmektedir. Aşağıda Jena Climate örneğinin üst üste üç kez SimpleRNN katmanının uygulandığı biçimi verilmiştir. #---------------------------------------------------------------------------------------------------------------------------- import pandas as pd import numpy as np PREDICTION_INTERVAL = 24 * 60 // 10 # 144 WINDOW_SIZE = 24 * 60 // 10 # 144 SLIDING_SIZE = 5 BATCH_SIZE = 32 EPOCHS = 200 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) from sklearn.preprocessing import OneHotEncoder ohe = OneHotEncoder(sparse_output=False) ohe.fit(df[['Month', 'Hour-Minute']]) ohe_result = ohe.transform(df[['Month', 'Hour-Minute']]) df = pd.concat([df, pd.DataFrame(ohe_result)], axis=1) df.drop(['Month', 'Hour-Minute'], axis=1, inplace=True) raw_dataset_x = df.to_numpy('float32') raw_dataset_y = df['T (degC)'].to_numpy('float32') from sklearn.model_selection import train_test_split raw_temp_dataset_x, raw_test_dataset_x, raw_temp_dataset_y, raw_test_dataset_y = train_test_split(raw_dataset_x, raw_dataset_y, test_size=0.1, shuffle=False) raw_training_dataset_x, raw_validation_dataset_x, raw_training_dataset_y, raw_validation_dataset_y = \ train_test_split(raw_temp_dataset_x, raw_temp_dataset_y, test_size=0.1, shuffle=False) from sklearn.preprocessing import StandardScaler ss = StandardScaler() ss.fit(raw_training_dataset_x) raw_scaled_training_dataset_x = ss.transform(raw_training_dataset_x) raw_scaled_validation_dataset_x = ss.transform(raw_validation_dataset_x) raw_scaled_test_dataset_x = ss.transform(raw_test_dataset_x) import tensorflow as tf from tensorflow.keras.utils import PyDataset class DataGenerator(PyDataset): def __init__(self, raw_x, raw_y, batch_size, pi, ws, ss, *, shuffle=True): super().__init__() self.raw_x = raw_x self.raw_y = raw_y self.batch_size = batch_size self.pi = pi self.ws = ws self.ss = ss self.shuffle = shuffle self.nbatches = (len(raw_x) - pi - ws) // batch_size // ss self.index_list = list(range((len(raw_x) - pi - ws) // ss)) def __len__(self): return self.nbatches def __getitem__(self, batch_no): x = np.zeros((self.batch_size, self.ws, self.raw_x.shape[1])) y = np.zeros(self.batch_size) for i in range(self.batch_size): offset = self.index_list[batch_no * self.batch_size + i] * self.ss x[i] = self.raw_x[offset:offset + self.ws] y[i] = self.raw_y[offset + self.ws + self.pi - 1] return tf.convert_to_tensor(x), tf.convert_to_tensor(y) def on_epoch_end(self): if self.shuffle: np.random.shuffle(self.index_list) from tensorflow.keras import Sequential from tensorflow.keras.layers import Input, SimpleRNN, Reshape, Dense model = Sequential(name='Jena-Climate') model.add(Input((WINDOW_SIZE, raw_training_dataset_x.shape[1]), name='Input')) model.add(SimpleRNN(128, activation='tanh', return_sequences=True, name='SimpleRNN')) 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='linear', name='Output')) model.summary() model.compile(optimizer='rmsprop', loss='mean_squared_error', metrics=['mean_absolute_error']) from tensorflow.keras.callbacks import EarlyStopping esc = EarlyStopping(monitor="val_loss", patience=10, restore_best_weights=True) dg_training = DataGenerator(raw_scaled_training_dataset_x, raw_training_dataset_y, BATCH_SIZE, PREDICTION_INTERVAL, WINDOW_SIZE, SLIDING_SIZE, shuffle=False) dg_validation = DataGenerator(raw_scaled_validation_dataset_x, raw_validation_dataset_y, BATCH_SIZE, PREDICTION_INTERVAL, WINDOW_SIZE, SLIDING_SIZE, shuffle=False) hist = model.fit(dg_training, validation_data = dg_validation, epochs=EPOCHS, verbose=1, 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 - Mean Squared Error', pad=10, fontsize=14) plt.xticks(range(0, 300, 10)) plt.plot(hist.epoch, hist.history['mean_absolute_error']) plt.plot(hist.epoch, hist.history['val_mean_absolute_error']) plt.legend(['MSE', 'Validation MSE']) plt.show() # evaluation dg_test = DataGenerator(raw_scaled_test_dataset_x, raw_test_dataset_y, BATCH_SIZE, PREDICTION_INTERVAL, WINDOW_SIZE, SLIDING_SIZE, shuffle=False) eval_result = model.evaluate(dg_test) for i in range(len(eval_result)): print(f'{model.metrics_names[i]}: {eval_result[i]}') # prediction predict_df = pd.read_csv('predict.csv') predict_df['Month'] = predict_df['Date Time'].str[3:5] predict_df['Hour-Minute'] = predict_df['Date Time'].str[11:16] predict_df.drop(['Date Time'], axis=1, inplace=True) ohe_result = ohe.transform(predict_df[['Month', 'Hour-Minute']]) predict_df = pd.concat([predict_df, pd.DataFrame(ohe_result)], axis=1) predict_df.drop(['Month', 'Hour-Minute'], axis=1, inplace=True) predict_dataset = predict_df.to_numpy('float32') scaled_predict_dataset = ss.transform(predict_dataset) predict_result = model.predict(scaled_predict_dataset.reshape(1, predict_dataset.shape[0], predict_dataset.shape[1])) for presult in predict_result[:, 0]: print(presult) #---------------------------------------------------------------------------------------------------------------------------- Yazısal verilerin zaman serilerine benzediğinden bahsetmiştik. Her ne kadar yazılarda sözcüklerin bir zaman bilgisi (time stamp) yoksa da sözcüklerin peşi sıra birbirini izelemesi onların zaman serilerine benzemesine yol açmaktadır. İşte bu nedenle geri beslemeli ağlar yalnızca zaman serilerinde değil aynı zamanda metinlerin anlamlandırılmasında da kullanılmaktadır. Biz metinler üzerinde işlemler yaparken Embedding katmanıyla sözcükleri vektörlerle ifade etmiştik. Her sözcük bir vektör (bir satır olarak düşünebiliriz) ile ifade edildiğine göre yazı da aslında vektörlerden oluşan bir matris biçiminde ele alınabilir. O halde biz yazılar üzerinde işlemler yapan sinir ağlarında önce yazıları Embedding katmanına sokup bu katmanın çıktısını da geri besleme katmanlarına verebiliriz. Böylece modelimizin katman yapısı aşağıdaki gibi olabilir: Yazı ---> Embedding ---> SimpleRNN ---> Dense ---> Dense ---> Çıktı #---------------------------------------------------------------------------------------------------------------------------- #---------------------------------------------------------------------------------------------------------------------------- Şimdi daha önce üzerinde çalıştığımız IMDB örneğini SimpleRNN katmanını kullanarak yeniden tasarlayalım. Modelin katman yapısı şöyle olabilir: TEXT_SIZE = 250 WORD_VECT_SIZE = 64 # .... 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(SimpleRNN(64, activation='tanh', return_sequences=True, name='SimpleRNN-1')) 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() 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ü sırasal değerler elde edilmiştir. Bu 64'lü girişler 64 nörondan oluşan SimpleRNN katmanına girdi yapılmış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 biriktirilmektedir. Sonra bunlar Reshape katmanı ile düzleştirilip Dense katmanlara verilmiştir. Bu örnekte biz yalnızca tek bir SimpleRNN katmanı kullandık. Burada birden fazla SimpleRNN katmanın kullanılması parametre sayısının aşırı artması nedeniyle bir "underfitting" olgusuna yol açabilmektedir. İzleyen paragraflarda eğitilebilir parametrelerin sayısının azaltılması için Dropbox regülasyonu ele alınmaktadır. Aşağıda bu uygulamanın tüm kodları verilmiştir. #---------------------------------------------------------------------------------------------------------------------------- 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, SimpleRNN, 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(SimpleRNN(64, activation='tanh', return_sequences=True, name='SimpleRNN-1')) 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') #---------------------------------------------------------------------------------------------------------------------------- #---------------------------------------------------------------------------------------------------------------------------- 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 değildir. #---------------------------------------------------------------------------------------------------------------------------- #---------------------------------------------------------------------------------------------------------------------------- Yapay sinir ağlarında "overfitting" ve "underfitting" durumunu azaltmak için kullanılan teniklere "düzenleme (regularization)" teknikleri denilmektedir. Bu bağlamda çeşitli düzenleme teknikleri geliştirilmiştir. Bunalardan önemli olanları şunlardır: - L1 (Lasso) ve L2 (Ridge) Düzenlemeleri - Dropout Düzenlemesi - Batch Normalization Düzenlemesi - Erken Sonlandırma (Early Stopping) Düzenlemesi - Verilen Çoğaltılması (Data Augmentation) Yoluyla Yapılan Düzenlemeler - Model Karmaşıklığını Azaltma Yoluyla Yapılan Düzenlemeler Biz bu yöntemlerden "erken sonlandırma (early stopping)" ve verilerin çolğaltılması (data augmentation)" konularını görmüştük. Anımsanacağı gibi erken sonlandırma eğitimdeki metrik değerlerle sınama değerlerinin birbirinden kopması durumunda epoch kaynaklı overfitting durumunu engellemek için kullanılıyordu. Verilerin çoğaltılması veri kümesinin büyütülmesi yoluyla "overfitting" ve "undefitting" olgusunun azaltılmasına katkı sağlıyordu. Biz L1 ve L2 düzenlemelerini daha sonra göreceğiz. #---------------------------------------------------------------------------------------------------------------------------- #---------------------------------------------------------------------------------------------------------------------------- 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 ezberlediğ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 olasılığını 0.1 yaptığımızda bu katmanın öncesindeki katmanda 100 nöron varsa bu durum bu 100 nöronun kesinlikle 10 tanesinin atılacağı anlamına gelmemektedir. Dropout düzenlemesi çıktı katmanı dışındaki tüm katmanlara uygulanabilmektedir. 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 her 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. Yani Keras'ın Dropout katmanı nöron atmayı epoch temelinde değil batch temelinde yapar. Tabii aslında nöronlar gerçek anlamda modelden atılmamaktadır. Yalnızca onların çıktıları 0'a çekilmektedir. Böylece dot-product işleminde işlemden 0 elde edilmektedir. Bu da nöron atılmış gibi bir etki oluşturmaktadı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 oranı 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. rate atılma oranını belirtmek üzere matematiksel olarak bu artırma 1 / (1 - rate) işleminden elde edilen değer kullanılarak yapılmaktadır. Tabii Keras'ta biz bu işlemi manuel olarak yapmayız. Zaten 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. Burada dikkat edilmesi gereken bir nokta şudur: Dropout işlemi nöron'un çıktısını sıfırlamamaktadır. Bir batch'lik işlemde 0 gibi göstermektedir. (Zaten Dropout katmanı önceki katmanın çıkışına uygulandığına göre önceki katmandaki nöronların ağırlıkları üzerinde bir değişiklik yapamamaktadır.) 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) result = dropout_layer(data, training=True) print(result) result = dropout_layer(data, training=True) print(result) #---------------------------------------------------------------------------------------------------------------------------- 74. Ders - 26/10/2024 - Cumartesi #---------------------------------------------------------------------------------------------------------------------------- #---------------------------------------------------------------------------------------------------------------------------- Şimdi de IMDB örneğinde Dropout düzenlemesinden faydalanalım. Modelimiz şöyle olabilir: Input --> Embedding --> Dropout (0.3) --> SimpleRNN --> Reshape --> Dropout(0.3) --> Dense ---> Dropout (0.3) --> Dropout (0.3) --> Dense (output) Model aşağıda verilmiştir. #---------------------------------------------------------------------------------------------------------------------------- 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, Dropout, SimpleRNN, 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(Dropout(0.3, name='Dropout-1')) model.add(SimpleRNN(64, activation='tanh', return_sequences=True, name='SimpleRNN-1')) model.add(Reshape((-1, ), name='Reshape')) model.add(Dropout(0.3, name='Dropout-2')) model.add(Dense(256, activation='relu', name='Hidden-1')) model.add(Dropout(0.3, name='Dropout-3')) model.add(Dense(256, activation='relu', name='Hidden-2')) model.add(Dropout(0.3, name='Dropout-4')) 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') #---------------------------------------------------------------------------------------------------------------------------- Geri beslemeli ağlarda (Recurrent Neural Networks) çıktının bir sonraki girdi ile işlemi sokulması ağa belli bir hafıza kazandırmaktadır. Ancak bu hafıza "gradyen kaybolması (vanishing gradient)" denilen 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şturmaktadır. Ya da 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. Daha önce de bahsettiğimiz gibi "gradyen kaybolması" ağın derinleşmesi sonucunda oluşan genel bir problemdir. Geri beslemeli ağlar aslında ağı derinleştirmektedir. Bunun sonucu olarak da bu ağlarda gradyen kaybolması daha açık bir biçimde kendini göstermektedir. İşte geçmişin ağda daha iyi hatırlanması için ve "gradyen kaybolması" denilen problemi azaltmak için bazı modeller önerilmiştir. Bunlardan en önemlilerinden biri LSTM (Long Short Term Memory) denilen 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ı engellemesini (gradyen kaybolmasını azaltmayı) hedeflemektedir. LSTM katmanındaki ağa eklenen ilave "carry" girişinin nasıl olup da "gradyen kaybolmasını" azalttığı konusu biraz karmaşıktır. LSTM katmanında her zamansal girdi önceki çıktı ve önceki "carry" değeri ile işleme sokulmaktadır. Dolayısıyla katmanın bir hücresindeki dot product işlemi şöyle yapılmaktadır: output_t = activation(np.dot(input_new, W) + np.dot(output_prev, U) + np.dot(carry_prev, V) + b) Buradaki dot product işleminin SimpleRNN'deki işlemden farkı np.dot(carry_prev, V) terimiyle temsil ettiğimiz "carry" girişidir. Bu "carry" girişi aslında üç farklı terimin birleşimiyle oluşturulmaktadır: carry_next = = i * k + carry_prev * f Burada aslında bizim carry ile belirttiğimiz girdi bir tane girdinin değil üç tane girdinin birleşimidir. Bu girdileri biz i, k ve f ile gösterdik. şte bu i, k ve f girdileri de şöyle oluşturulmaktadır: i = activation(np.dot(output_prev, Ui) + np.dot(input_prev, Wi) + bi) f = activation(np.dot(output_prev, Uf) + np.dot(input_prev, Wf) + bf) k = activation(np.dot(output_prev, Uk) + np.dot(input_prev, Wk) + bk) Görüldüğü gibi aslında LSTM katmanınında i, j ve k biçiminde temsil ettiğimiz üç giriş vardır. Bu i, k ve f girişleri modele Ui, Uf, Uk, Wi, Wf ve Wk ağırlık değerlerini eklemektedir. Pekiyi bu durumda böyle bir LSTM katmanında eğitilebilir kaç parametre bulunacaktır? n LSTM katmanına giren nöron sayısı, m de LSTM katmanındaki nöron sayısı olsun. Bu durumda: - W matrisinin eleman sayısı n * m tanedir. - U matrisinin eleman sayısı m * m tanedir - Ui, Uf ve Uk matrislerinin eleman sayıları ise m * m tanedir. - Wi, Wf ve Wk matrislerinin eleman sayıları ise n * m tanedir. - Bu katmandaki toplam "bias" değerlerinin sayısı da m * b + m * bi + m * bf + m * bk tanedir. Yukarıdaki tüm değerler toplandığında ve 4 parantezine alındığında LSTM katmanındaki eğitilebilir parametrelerin sayısı da 4 * (n * m + m * m + m) olacaktır. Keras'taki LSTM sınıfının __init__ metodunun parametrik yapısı şöyledir: tf.keras.layers.LSTM( units, activation='tanh', recurrent_activation='sigmoid', use_bias=True, kernel_initializer='glorot_uniform', recurrent_initializer='orthogonal', bias_initializer='zeros', unit_forget_bias=True, 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, seed=None, return_sequences=False, return_state=False, go_backwards=False, stateful=False, unroll=False, use_cudnn='auto', **kwargs ) Burada yine ilk parametre katmandaki nöron sayısını belirtmektedir. Aktivasyon fonksiyonu yine default olarak "tanh" biçiminde alınmıştır. Yine katmandaki çıktıların biriktirilmesi için kullanılan return_sequences parametresi vardır. Yani katmanın kullanımı SimpleRNN katmanına oldukça benzemektedir. #---------------------------------------------------------------------------------------------------------------------------- #---------------------------------------------------------------------------------------------------------------------------- Şimdi LSTM katmanındaki toplam eğitilebilir parametrelerin sayısına basit bir modelle bakalım. Modelimiz şöyle olsun: from tensorflow.keras import Sequential from tensorflow.keras.layers import Input, LSTM, Reshape, Dense model = Sequential(name='SimpleRNN-Test') model.add(Input((100, 10), name='Input')) model.add(LSTM(128, return_sequences=True, name='LSTM')) 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='linear', name='Output')) model.summary() Model elde edilen özet çıktı şöyledir: Model: "SimpleRNN-Test" ┌─────────────────────────────────┬────────────────────────┬───────────────┐ │ Layer (type) │ Output Shape │ Param # │ ├─────────────────────────────────┼────────────────────────┼───────────────┤ │ LSTM (LSTM) │ (None, 100, 128) │ 71,168 │ ├─────────────────────────────────┼────────────────────────┼───────────────┤ │ Reshape (Reshape) │ (None, 12800) │ 0 │ ├─────────────────────────────────┼────────────────────────┼───────────────┤ │ Hidden-1 (Dense) │ (None, 256) │ 3,277,056 │ ├─────────────────────────────────┼────────────────────────┼───────────────┤ │ Hidden-2 (Dense) │ (None, 256) │ 65,792 │ ├─────────────────────────────────┼────────────────────────┼───────────────┤ │ Output (Dense) │ (None, 1) │ 257 │ └─────────────────────────────────┴────────────────────────┴───────────────┘ Total params: 3,414,273 (13.02 MB) Trainable params: 3,414,273 (13.02 MB) Non-trainable params: 0 (0.00 B) Biz yukarıda LSTM katmanındaki toplam eğitilebilir parametrelerin sayısının n katmana giren nöron sayısı, m de katmandan çıkan nöron sayısı olmak üzere 4 * (n * m + m * m + m) biçiminde olduğunu belirtmiştik. Bu örnekte n = 10, m = 128'dir. O halde 4 * (10 * 128 + 128 * 128 + 128) = 71168'dir. Diğer katmalardaki eğitilebilir parametrelerin sayısı daha önceleri yaptığımız biçimde hesaplanmaktadır. #---------------------------------------------------------------------------------------------------------------------------- #---------------------------------------------------------------------------------------------------------------------------- Aşağıda daha önce yapmış olduğumuz IMDB örneğinin LSTM ile yeniden gerçekleştirimi verilmişti. Modeli şöyle oluşturduk: from tensorflow.keras import Sequential from tensorflow.keras.layers import Input, TextVectorization, Embedding, LSTM, Dropout, Dense, Reshape tv = TextVectorization(output_sequence_length=TEXT_SIZE, output_mode='int') tv.adapt(dataset_x) model = Sequential(name='IMBD-LSTM') model.add(Input((1, ), dtype='string', name='Input')) model.add(tv) model.add(Embedding(tv.vocabulary_size(), WORD_VECT_SIZE, name='Embedding')) model.add(Dropout(0.3, name='Dropout-1')) model.add(LSTM(64, return_sequences=True, name='LSTM')) model.add(Reshape((-1, ), name='Reshape')) model.add(Dropout(0.3, name='Dropout-2')) model.add(Dense(256, activation='relu', name='Hidden-1')) model.add(Dropout(0.3, name='Dropout-3')) model.add(Dense(256, activation='relu', name='Hidden-2')) model.add(Dropout(0.3, name='Dropout-4')) model.add(Dense(1, activation='sigmoid', name='Output')) model.summary() Burada TextVectorization katmanı kullanılmıştır. LSTM katmanında toplam 128 nöron kullandık. Overfitting problemini azaltmak için katmanların aralarına Dropout katmanlarını da yerleştirdik. Modelin özet çıktısı şöyledir: Model: "IMBD-LSTM" ┌─────────────────────────────────┬────────────────────────┬───────────────┐ │ Layer (type) │ Output Shape │ Param # │ ├─────────────────────────────────┼────────────────────────┼───────────────┤ │ text_vectorization_4 │ (None, 150) │ 0 │ │ (TextVectorization) │ │ │ ├─────────────────────────────────┼────────────────────────┼───────────────┤ │ Embedding (Embedding) │ (None, 150, 64) │ 11,630,208 │ ├─────────────────────────────────┼────────────────────────┼───────────────┤ │ Dropout-1 (Dropout) │ (None, 150, 64) │ 0 │ ├─────────────────────────────────┼────────────────────────┼───────────────┤ │ LSTM (LSTM) │ (None, 150, 64) │ 33,024 │ ├─────────────────────────────────┼────────────────────────┼───────────────┤ │ Reshape (Reshape) │ (None, 9600) │ 0 │ ├─────────────────────────────────┼────────────────────────┼───────────────┤ │ Dropout-2 (Dropout) │ (None, 9600) │ 0 │ ├─────────────────────────────────┼────────────────────────┼───────────────┤ │ Hidden-1 (Dense) │ (None, 256) │ 2,457,856 │ ├─────────────────────────────────┼────────────────────────┼───────────────┤ │ Dropout-3 (Dropout) │ (None, 256) │ 0 │ ├─────────────────────────────────┼────────────────────────┼───────────────┤ │ Hidden-2 (Dense) │ (None, 256) │ 65,792 │ ├─────────────────────────────────┼────────────────────────┼───────────────┤ │ Dropout-4 (Dropout) │ (None, 256) │ 0 │ ├─────────────────────────────────┼────────────────────────┼───────────────┤ │ Output (Dense) │ (None, 1) │ 257 │ └─────────────────────────────────┴────────────────────────┴───────────────┘ Total params: 14,187,137 (54.12 MB) Trainable params: 14,187,137 (54.12 MB) Program bir bütün olarak aşağıda verilmiştir. #---------------------------------------------------------------------------------------------------------------------------- TEXT_SIZE = 150 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, LSTM, Dropout, Dense, Reshape tv = TextVectorization(output_sequence_length=TEXT_SIZE, output_mode='int') tv.adapt(dataset_x) model = Sequential(name='IMBD-LSTM') model.add(Input((1, ), dtype='string', name='Input')) model.add(tv) model.add(Embedding(tv.vocabulary_size(), WORD_VECT_SIZE, name='Embedding')) model.add(Dropout(0.3, name='Dropout-1')) model.add(LSTM(64, return_sequences=True, name='LSTM')) model.add(Reshape((-1, ), name='Reshape')) model.add(Dropout(0.3, name='Dropout-2')) model.add(Dense(256, activation='relu', name='Hidden-1')) model.add(Dropout(0.3, name='Dropout-3')) model.add(Dense(256, activation='relu', name='Hidden-2')) model.add(Dropout(0.3, name='Dropout-4')) 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') #---------------------------------------------------------------------------------------------------------------------------- 75. Ders - 27/10/2024 - Pazar #---------------------------------------------------------------------------------------------------------------------------- #---------------------------------------------------------------------------------------------------------------------------- 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 __init__ metodunun parametrik yapısı da LSTM sınıfına benzemektedir: tf.keras.layers.GRU( units, activation='tanh', recurrent_activation='sigmoid', 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, seed=None, return_sequences=False, return_state=False, go_backwards=False, stateful=False, unroll=False, reset_after=True, use_cudnn='auto', **kwargs ) Yine metodun birinci parametresi katmandaki nöron sayısını, ikinci parametresi ise aktivasyon fonksiyonunu belirtmektedir. LSTM ile GRU arasında şu farklılıklar söz konusudur: - LSTM'de ağa uzun dönem hafıza kazandırmak için uygulanan giriş üç bileşene sahipken GRU katmanında iki bileşene sahiptir. Dolayı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 veri kümesi için verdiğimiz LSTM örneğini GRU için aşağıdaki gibi yeniden düzenleyebiliriz. Buradaki tek yaptığımız şey LSTM yerine GRU katmanını kullanmaktır. Bu iki katmanın içsel çalışması farklı olsa da arayüz olarak aynıdır: from tensorflow.keras import Sequential from tensorflow.keras.layers import Input, TextVectorization, Embedding, GRU, Dropout, Dense, Reshape tv = TextVectorization(output_sequence_length=TEXT_SIZE, output_mode='int') tv.adapt(dataset_x) model = Sequential(name='IMBD-LSTM') model.add(Input((1, ), dtype='string', name='Input')) model.add(tv) model.add(Embedding(tv.vocabulary_size(), WORD_VECT_SIZE, name='Embedding')) model.add(Dropout(0.3, name='Dropout-1')) model.add(GRU(64, return_sequences=True, name='GRU')) model.add(Reshape((-1, ), name='Reshape')) model.add(Dropout(0.3, name='Dropout-2')) model.add(Dense(256, activation='relu', name='Hidden-1')) model.add(Dropout(0.3, name='Dropout-3')) model.add(Dense(256, activation='relu', name='Hidden-2')) model.add(Dropout(0.3, name='Dropout-4')) model.add(Dense(1, activation='sigmoid', name='Output')) model.summary() Aşağıda örnek bir bütün olarak verilmiştir. #---------------------------------------------------------------------------------------------------------------------------- TEXT_SIZE = 150 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, GRU, Dropout, Dense, Reshape tv = TextVectorization(output_sequence_length=TEXT_SIZE, output_mode='int') tv.adapt(dataset_x) model = Sequential(name='IMBD-LSTM') model.add(Input((1, ), dtype='string', name='Input')) model.add(tv) model.add(Embedding(tv.vocabulary_size(), WORD_VECT_SIZE, name='Embedding')) model.add(Dropout(0.3, name='Dropout-1')) model.add(GRU(64, return_sequences=True, name='GRU')) model.add(Reshape((-1, ), name='Reshape')) model.add(Dropout(0.3, name='Dropout-2')) model.add(Dense(256, activation='relu', name='Hidden-1')) model.add(Dropout(0.3, name='Dropout-3')) model.add(Dense(256, activation='relu', name='Hidden-2')) model.add(Dropout(0.3, name='Dropout-4')) 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') #---------------------------------------------------------------------------------------------------------------------------- Geri beslemeli ağlarda biz önceki çıktıyı sonraki girdi ile ilişkilendiriyorduk. SimpleRNN katmanında "gradyen kaybolması (vanishing gradient)" denilen problem yüzünden geçmişe ilişkin hafız iyi bir biçimde tutulamıyordu. 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 olamamaktadı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. Ya da ö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ükanın baştan eczane olduğu bilinse daha iyi bir çıkarım yapılabilir. (Bazı doğal dillerin dillerin gramerleri de bu biçimde gelişmiştir. Ö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 masanın üzerinde olduğu baştan anlaşılmaktadır. Ancak İngilizce'de de yüklem hemen özneden sonra gelir. Böylece bir kişinin ne yaptığı baştan anlaşılmaktadır.) İşte geri beslemede önceki çıkışın sonraki girişle ilişkilendirilmesinin yanı sıra bunun tersinin de yapılması yani sonraki çıkışın önceki girişle ilişkilendirilmesinin de sağlanması daha iyi bir öğrenmeye yol açabilektedir. Bu tür geri beslemeli ağlara "çift yönlü (bidriectional)" geri beslemeli ağlar denilmektedir. Mimari olarak çift yönlü geri beslemeli ağlar tek yönlü ağlara geri doğru aynı biçimde bir besleme eklenmesiyle oluşturulmaktadır. Ancak çift yönlü geri besleme ağı daha karmaşık bir hale getirmektedir. Dolayısıyla eğitilebilir parametrelerin sayısını da artırmaktadır. Ancak bazı uygulamalarda daha iyi bir sonucun elde edilmesine olanak sağlamaktadır. Tabii zamansal verilerin söz konusu olduğu bazı uygulamalarda çift yönlü geri besleme bir fayda sağlamadığı gibi modelin başarısını bile düşürmektedir. Örneğin Jena Cliamate veri kümesinde çift yönlü bir geri beslemenin açık bir faydası olmayacaktır. Çünkü Jena Climate veri kümesinde gelecekteki bilginin geçmiş ile yeniden ilişkilendirilmesinin açık bir yaydası yoktur. Dolayısıyla çift yönlü geri beslemenin her zaman tek yönlü geri beslemeden daha iyi sonuç vereceği söylenemez. Uygulamacının gerektiğinde her iki yöntemi de denemesi tavsiye edilmektedir. Yukarıda da belirttiğimiz gibi Jena Climate örneğinde olduğu gibi pek çok zaman serisi tarzındaki veri kümelerinde çift yönlü geri besleme açık bir fayda sağlamamaktadır. Çift yönlü geri beslemenin en sık kullanıldığı alan "makine çevirisi", "metinden anlam çıkartma" gibi metinsel işlemlerdir. #---------------------------------------------------------------------------------------------------------------------------- #---------------------------------------------------------------------------------------------------------------------------- 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 kalıbı (decorator pattern) biçiminde oluşturulmuştur. Biz bu sınıfa SimpleRNN, 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 ) Metodun birinci parametresi kullanılacak geri tek yönlü geri besleme katman nesnesini almaktadır. Aslında Keras'ın içsel tasarımında SimpleRNN, LSTM ve GRU katmanları RNN isimli bir sınıftan türetilmiştir. Dolayısıyla bu parametre için RNN sınıfından türetilmiş bir katmana ilişkin katman nesnesi girilmelidir. Örneğin: model.add(Bidirectional(LSTM(64, name='LSTM', return_sequences=True), name='Bidirectional')) Burada Bidirectional fonksiyonun birinci parametresi LSTM nesnesi olarak girilmiştir. Yani Bidirectional sınıfı tek yönlü geri beslemeli sınıfların çift yönlü çalışmasını sağlamaktadır. Bu durumda bizim ağı çift yönlü yapmak için tek yapacağımız şey SimpleRNN, LSTM ya da GRU katman nesnesini Bidirectional katmanına vermektir. 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ğıda daha önce yapmış olduğumuz LSTM geri beslemeli IMDB örneğinin çift yönlü hale getirilmiş biçimi verilmiştir. Aynı örneği ilgili satırı aşağıdaki hale getirip GRU için de kullanabilirsiniz: model.add(Bidirectional(GRU(64, return_sequences=True, name='LSTM'), name='Bidrectional')) #---------------------------------------------------------------------------------------------------------------------------- TEXT_SIZE = 150 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, Bidirectional, LSTM, Dropout, Dense, Reshape tv = TextVectorization(output_sequence_length=TEXT_SIZE, output_mode='int') tv.adapt(dataset_x) model = Sequential(name='IMBD-LSTM') model.add(Input((1, ), dtype='string', name='Input')) model.add(tv) model.add(Embedding(tv.vocabulary_size(), WORD_VECT_SIZE, name='Embedding')) model.add(Dropout(0.3, name='Dropout-1')) model.add(Bidirectional(LSTM(64, return_sequences=True, name='LSTM'), name='Bidrectional')) model.add(Reshape((-1, ), name='Reshape')) model.add(Dropout(0.3, name='Dropout-2')) model.add(Dense(256, activation='relu', name='Hidden-1')) model.add(Dropout(0.3, name='Dropout-3')) model.add(Dense(256, activation='relu', name='Hidden-2')) model.add(Dropout(0.3, name='Dropout-4')) 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') #---------------------------------------------------------------------------------------------------------------------------- Biz kursumuzda daha önce "aktarımsal öğrenme (transfer learning)" konusuna bir giriş yapmıştık. Aktarımsal öğrenme "daha önceden eğitilmiş olan modellerden faydalanılması" anlamına geliyordu. İşte makine öğrenmesi alanında framewrok'ler ve birtakım topluluklar önceden hazırlanmış ve eğitilmiş çeşitli modelleri bulundurabilmektedir. Böylece uygulamacı başarısı kanıtlanmış olan modelleri sıfırdan oluşturmak yerine zaten oluşturulmuş ve eğitilmiş bu modellerden faydalanabilmektedir. Başkaları tarafından oluşturulmuş modeller yalnızca model olarak (yani eğitilmemiş bir biçimde) karşımıza çıkabileceği gibi eğitilmiş bir biçimde de karşımıza çıkabilir. Yani biz başkaları tarafından hazırlanmış modelleri yalnızca model olarak kullanabiliriz ya da onlar zaten eğitilmişse onların eğitim sonucunda oluşturulmuş olan ağırlıklarını da kullanabiliriz. Başkaları tarafından hazırlanmış olan modelleri kullanırken modellerin sunum biçimine dikkat edilmesi gerekir. Modeller genellikle framework'e özgü bir biçimde oluşturulmaktadır. Örneğin biz PyTorch için oluşturulmuş bir modeli Tensorflow'da kullanamayız. Dış dünyaya sunulmuş modellerde dosya formatlarına da dikkat etmek gerekir. Farklı framework'ler farklı dosya formatlarını kullanmaktadır. Dolayısıyla bu dosyaların Python'da kullanıma hazır hale getirilmesinde de framework'e özgü sınıflardan ve fonksiyonlardan faydalanılmaktadır. Biz kursumuzda şimdiye kadar Keras kullandık. Keras eskiden birden fazla backend ile çalışabiliyordu. Ancak daha sonra tamamen Tensorflow ile entegre edildi. Fakat Keras aynı zamanda bağımsız bir proje olarak da devam ettirilmektedir. Maalesef bağımsız Keras projesi ile Tensorflow içerisine entegre edilmiş olan Keras projesi arasında uyumsuzluklar da oluşmaya başlamıştır. #---------------------------------------------------------------------------------------------------------------------------- #---------------------------------------------------------------------------------------------------------------------------- Pekiyi biz Tensorflow'daki Keras'ta çalışırken hazır modelleri nasıl elde edebiliriz? Hazır modellerin bazıları zaten framework'ün içerisinde sınıflar biçiminde bulundurulmuştur. En kolay yöntem bu sınıfların kullanılmasıdır. Çünkü bu sınıflar framework ile tam bir uyum içerisinde çalışmaktadır. Keras'ın hazır modelleri "tensorflow.keras.applications" paketi içerisindedir. (Eskiden bu paket Tensorflow'a resmi olarak dahil değildi. Sonra resmi olarak dahil edildi.) Ancak Keras'ın bu paketleri ağırlıklı olarak resimsel uygulamalara yöneliktir. Framework'lerin kendi hazır model sınıflarının yanı sıra çeşitli toplulukların bünyesinde oluşturulmuş olan hazır modeller de bulunmaktadır. Örneğin Tensorflow (dolayısıyla Google) tarafından oluşturulmuş olan "Tensorflow Hub" denilen bir topluluk vardır. Bu topluluğun üyeleri kendi hazırladıkları modellerini dış dünyaya açmaktadır. Geçen yıl bu topluluk Kaggle'ın bünyesine dahil edilmiştir. Kaggle zaman içerisinde Tensorflow dışında pek çok framework'e ilişkin modelleri barındıran vir topluluk haline gelmiştir. Dolayısıyla Kaggle bu tür hazır modeller için iyi bir kaynak oluşturmaktadır. Kaggle'ın modelleri barındıran bağlantısı aşağıda verilmiştir: https://www.kaggle.com/models #---------------------------------------------------------------------------------------------------------------------------- #---------------------------------------------------------------------------------------------------------------------------- Yukarıda da belirttiğimiz gibi bir framework için oluşturulmuş model başka bir framework'te kullanılamamaktadır. Bu nedenle Kaggle gibi topluluk sitelerinde model araştırırken modelin oluşturulduğu framework'e dikkat ediniz. Bu tür topluluk sitelerinde modeller framework temelinde filtrelenerek aranabilmektedir. Model barındıran topluluk sitelerinde modellerin bir dosya biçiminde indirilip kullanılmasına olanak sağlamaktadır. Ancak kulanım kolaylığı oluşturabilmek bu tür sitelerimn bazıları URL temelli yüklemelere de izin verilebilmektedir. Böylece modelin indirilip yüklenmesi tek hamlede ilgili URL belirtilerek de yapılabilmektedir. Kaggle topluluğu modelleri ve dosyaları uzaktan indirmek için ayrı bir kütüphane de sunmaktadır. Bu kütüphaneye "kagglehup" kütüphanesi denilmektedir. Ancak bu kütüphanenin kullanılabilmesi için "API Key" oluşturulması gerekmektedir. kagglehup kütüophanesinin genel kullanımı aaşağıdaki bağlantıda açıklanmaktadır: https://github.com/Kaggle/kagglehub #---------------------------------------------------------------------------------------------------------------------------- #---------------------------------------------------------------------------------------------------------------------------- Bir topluluktan (örneğin Kaggle) bir model indirecekseniz yalnızca onun oluşturulduğu framework'e değil aynı zamanda indirilecek model dosyasının dosya formatına ve içeriğinde de dikkat etmeniz gerekir. Bazı dosya formatları genel amaçlıdır, içerisine değişik formatta bilgiler yerleştirilebilmektedir. Örneğin H5 formatı (Hierarchical Data Format)" genel amaçlı bir formattır. ".h5" uzantılı bir dosyanın içerisinde Keras uyumlu bir model bilgisinin bulunma zorunluluğu yoktur. Örneğin "protobuf formatı (Protocol Buffer Format)" da genel amaçlı bir formattır. ".pb" uzantılı bir dosyanın içinde ne olduğunun ayrıca biliniyor olması gerekir. Tensorflow dünyasında çok karşılaşılan dosyalar ve formatlar şunlardır: - H5 Dosyaları: Genellikle bu dosyalar içerisinde Tensorflow modelinin kendisi ve ağırlıkları ya da yalnızca ağırlıkları bulunabilmektedir. Bu dosyalar genellikle ".h5" ya da ".hdf5" uzantısıyla bulunur. - Keras Dosyaları: Eskiden Keras'ta model saklamak için H5 formatı yoğun kullanılıyordu. Sonra Keras ekibi Keras'a özgü bir biçimde ".keras" uzantılı dosya formatını kullanmaya başladı. Aslınde ".keras" uzantılı dosyalar özel bir formata sahip değildir. Bu dosyalar bir dizin'in zip'lenmesinden oluşmaktadır. ".keras" dosyasının belirttiği dizin içerisinde H5 dosyasının yanı sıra bazı JSON dosyaları (ve duruma göre bazı grafik dosyalar vs.) da bulunur. Yani aslında model bilgilerini yine H5 dosyasında tutulmaktadır. - SavedModel Formatı: Bu format Tensorflow kütüphanesi tarafından kullanılan diğer bir model sakalama formatıdır. Genellikle ".pb uzantılı (Protobcol Buffer Format)" dosyaların içerisine yerleştirilmektedir. - TFlite Formatı: Bu format ve dosya uzantısı Tensorflow modellerini ve ağırlık değerlerini saklamak için tasarlanmış diğer bir formattır. Özellikle düşük kapasiteli mobil aygıtlarda ve gömülü sistemlerde tercih edilmektedir. Bu format yalnızca makine öğrenmesinde değil özellikle mobil aygıtlarda da başka amaçlarla kullanılabilmektedir. Format diskte daha az yer kaplayacak biçimde tasarlanmıştır. #---------------------------------------------------------------------------------------------------------------------------- #---------------------------------------------------------------------------------------------------------------------------- 76. Ders - 02/11/2024 - Cumartesi #---------------------------------------------------------------------------------------------------------------------------- #---------------------------------------------------------------------------------------------------------------------------- Pekiyi yukarıdaki paragrafta açıkladığımız dosyalar ve formatlar Keras içerisinden nasıl kullanılmaktadır? - Biz daha önce ".h5" dosyalarını ve ".keras" uzantılı dosyaları Sequential model sınıfının save metodu ile oluşturmuştuk. İşte bu dosyalar ve içerisindeki modeller tensorflow.keras.models modülündeki load_model fonksiyonuyla geri yüklenebilmektedir. Zaten biz bu fonksiyonu daha önce de kullanmıştır. O halde elimizde ".h5" uzantılı ya da ".keras" uzantılı bir model varsa biz bunu bu load_model fonksiyonuyla yükeleyebiliriz. Bu fonksiyon bize bir Model nesnesi vermektedir. Örneğin: model = load_model('iris.keras') - #---------------------------------------------------------------------------------------------------------------------------- #---------------------------------------------------------------------------------------------------------------------------- Örneğin kaggle.com sitesindeki Models sekmesine girip "framework" için Keras seçildiğinde karşımıza çeşitli amaçlarla başkaları tarafından oluşturulmuş olan eğitilmiş ya da eğitilmemiş modeller çıkacaktır. Biz framework olarak Keras'ı seçtiğimiz için genellikle buradaki model dosyaları ".keras" uzantılı biçimde karşımıza çıkacaktır. Biz de bu dosyaları indirip yukarıda belirttiğimiz gibi load_model fonksiyonuyla yükleyebiliriz. Örneğin aşağıdaki siteden modeli indirdiğimizde "ResNet50.keras" isminde bir dosya elde etmiş olacağız: https://www.kaggle.com/models/paripatel2709/resnet Bu dosyayı da yularıda belirttiğimiz gibi load_model fonksiyonuyla yükleyebiliriz: from tensorflow.keras.models import load_model model = load_model('ResNet50.keras') model.summary() #---------------------------------------------------------------------------------------------------------------------------- from tensorflow.keras.models import load_model model = load_model('ResNet50.keras') model.summary() #---------------------------------------------------------------------------------------------------------------------------- Şimdi Keras içerisinde tensorflow.keras.applications paketinde hazır bir biçimde bulunan modellerin nasıl yüklenerek kendi amaçlarımız doğrultusunda kullanılabileceğine bazı örnekler verelim. Resim sınıflandırma ve anlamlandırmada kabul görmüş olan en önemli modellerden ikisi ResNet ve VGG modelleridir. Bu modeller onlarca katmana sahip olan çok ayrıntılı modellerdir. Biz burada bu modellerin iç yapısı üzerinde durmayacağız. Ancak bu modelleri açıklayan pek çok kaynak bulunmaktadır. Keras içeisindeki ResNet modellerinin yanında bazı sayılar bulunmaktadır. Örneğin ResNet50, ResNet101, ResNet152 gibi. Bu sayılar modelin katman sayısı ile ilgilidir. Yüksek sayılarda daha fazla katman vardır. Dolayısıyla daha fazla eğitilebilir parametre bulunmaktadır. 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. DenseNet121 sınıfının __init__ metodunun parametrik yapısı şöyledri: tf.keras.applications.DenseNet121( include_top=True, weights='imagenet', input_tensor=None, input_shape=None, pooling=None, classes=1000, classifier_activation='softmax' ) Metodun birinci parametresi True geçilirse model girdi ve çıktı katmanıyla bitlikte bir bütün olarak kullanılır. Genellikle bu parametre False biçimde geçilir. Çünkü genellikle uygulamacılar modeli bir bütün olarak kullanmak yerine modeli kendi amaçları doğrultusunda kullanırlar ve ince ayar (fine-tuning) yapmak isterler. Metodun weights parametresi önceden eğitimle elde edilmiş olan ağırlıkların kullanılıp kullanılmayacağını belirtmektedir. Burada biz nereden elde edilen ağırlıkların kullanılacağını belirtiriz. Bu parametre default olarak "imagenet" biçiminde girilmiştir. ImageNet resimlerden oluşan dev bir veritabanıdır. Bu veritabanı özellikle makine öğrenmesinde resimlerle ilgili işlemler yapan modellerin eğitilmesinde yaygın biçimde kullanılmaktadır. Burada weights parametresi None geçilirse model eğitilmemiş bir biçimde kullanılır. Yani bu durumda tüm eğitimi uygulamacının kendisi yapmak zorundadır. Bu parametreye ağırlıkların bulunduğu desteklenen bir formattaki dosyanın yol ifadesi de geçirilebilmektedir. Metodun input_shape parametresi girdi resimlerinin boyutunu belirtmektedir. Burada önemli bir noktayı da belirtmek istiyoruz. Biz "ImageNet" veritabanındaki resimlerden elde edilen ağırlıkları kullanmak istediğimizi düşünelim. Bu veritabanındaki eğitimler (224, 224, 3) boyutundaki resimlerle yapılmıştır. Eğer bizim resimlerimiz bu boyuttan büyük ise ya da küçük ise dönüştürme sırasında performans kayıpları oluşabilecektir. Bu nedenle bu sınıfları kullanıyorsanız eğitimin yapıldığı orijinal resim boyutuna ne kadar yakın bir boyut seçerseniz performans daha daha iyi olacaktır. Örneğin biz CIFAR-100 örneği için ResNet121 modelini kullanmak isteyelim. Ancak önceden eğitilmiş ağırlık değerleri yerine modelimizi biz kendi verilerimizle eğitmek isteyelim. Bu durumda ResNet121 nesnesi aşağıdaki gibi yaratılabilir. Eğer bu katmanın önünde bir girdi katmanı bulundurulacaksa bu durumda image_shape parametresi hiç girilmeyebilir. from tensorflow.keras.applications.densenet import DenseNet121 dn121 = DenseNet121(include_top=False, weights=None, input_shape=(32, 32, 3)) Burada include_top parametresi False geçildiği için modelin çıktı katmanını bizim oluşturmamız gerekir. Pekiyi biz bu hazır modeli nasıl Cifar-100 örneğinde kullanabiliriz? Daha önceden de belirttiğimiz gibi bu tür hazır modellerin Keras'ta fonksiyonel bir biçimde kullanılması tavsiye edilmektedir. Ancak biz burada önce klasik Sequential modeli kullanacağız sonra fonksiyonel model ile örnek vereceğiz. Önceden oluşturulmuş Keras modeli adeta bir katman gibi Sequential modele eklenmelidir. Zaten Model sınıflarının kendisi de aynı zamanda bir katman gibi kullanılabilmektedir. (Model sınıfın da aynı zamanda çoklu bir biçimde Layer sınıfından türetilmiş olduğunu anımsayınız.) model = Sequential(name='ResNet121-Cifar-100') model.add(Input((32, 32, 3), name='Input')) model.add(DenseNet121(include_top=False, weights=None, input_shape=(32, 32, 3), name='DenseModelTest')) model.add(Reshape((-1, ))) model.add(Dense(128, activation='relu', name='Dense-1')) model.add(Dense(128, activation='relu', name='Dense-2')) model.add(Dense(100, activation='softmax', name='Output')) model.summary() Burada önce modele bir Input katmanı eklenmiştir. Sonra da Dense121 modelinin tamamı adeta bir katman gibi modele eklenmiştir. Biz ayrıca bu hazır modelin ucuna iki Dense katman ve bir de çıktı katmanı ekledik. Artık modeli compile edip fit işlemi uygulayabiliriz. Bu örnekte önceden eğitilmiş modelin ağırlıklarını kullanmadığımıza dikkat ediniz. Burada aslında biz Dense121 nesnesi yaratılırken input_shape parametresini girmeyebilirdik. Çünkü modelimizin bir girdi katmanı olduğu için bu sınıf bu girdi katmanından hareketle zaten input_shape parametresini belirleyebilmektedir. Eğer biz hem girdi katmanı kullanıyorsak hem de bu input_shape parametresine argüman giriyorsak bu durumda bu iki resim boyutunun aynı olması gerekmektedir. Biz yukarıdaki örnekte yalnızca modelin kendisinden faydalanmak istedik. Tabii önceden eğitilmiş modelin ağırlıklaırnı da kullanabiliriz. Bunun için Dense121 nesnesinde weights parametresi 'imagenet" biçiminde geçilmelidir: model.add(DenseNet121(include_top=False, weights='imagenet', input_shape=(32, 32, 3), name='DenseModelTest')) Biz DenseNet121 katmanında (bu aslında aynı zamanda katmanlardan oluşan model nesnesidir) önceden elde edilmiş ağırlıkları kullandığımızda artık kendi verilerimizle yaptığımız eğitimde bu katmanların eğitime dahil edilmemesini isteyebiliriz. Çünkü "imagenet" o kadar büyük bir veritabanıdır ki bizim kendi resimlerimizin bu ağırlıkları fayda sağlayacak değiştirmesi de oldukça güçtür. Bu nedenle biz bu katmanı eğitim işleminden muaf hale getirmek için katman sınıflarının trainable parametresinden faydalanabiliriz. Örneğin: model.add(DenseNet121(include_top=False, weights='imagenet', input_shape=(32, 32, 3), trainable=False, name='DenseModelTest')) Burada artık trainable=False argümanı kullanıldığı için kendi verilerimiz ile eğitim yapılırken bu katmandaki tüm katmanlar eğitimden muaf tutulacaktır. Tabii test ve kestirim işlemlerinde kullanılacaktır. Bu hazır katmanı eğitimden muaf tutmanın bir avantajı da eğitim sırasında geçen zamanın kısaltılmasıdır. Tabii buradaki DenseNet121 katmanın içerisinde katmanların da yalnızca bir bölümü için trainable=False işlemi de yapılabilir. Daha öncedne de belirttiğimiz gibi hazır ağılıkların kullanılmasından sonra ayrıca modeli birkaç katman ekleyerek kendi veri kümemiz için eğitmeye "ince ayar (fine-tuning)" yapılması denilmektedir. Dense121 sınıfının ğırlıkları toplam 1000 tane sınıf için uygulanan eğitimle oluşturulmuştur. Bu 1000 tane sınıf içerisinde pek çok farklı temadan resimler bulunmaktadır. #---------------------------------------------------------------------------------------------------------------------------- #---------------------------------------------------------------------------------------------------------------------------- Aşağıda DenseNet121 sınıfının CIFAR-100 örneğinde Sequantial modeli ile kullanımına bir örnek verilmiştir. Bu örnekte önceden eğitilmiş modelin ağırlıkları kullanılmıştır. Burada verdiğimiz örnekten elde edilen başarı %65 civarındadır. Buradaki önemli bir handikap CIFAR-100 resimlerinin gerçek eğitimde kullanılan 224x224x3'lük resimlere göre oldukça küçük olmasıdır. Resim büyütülüp orijinal boyuta yaklaştırıldıkça performans da artacaktır. #---------------------------------------------------------------------------------------------------------------------------- from tensorflow.keras.applications.densenet import DenseNet121 from tensorflow.keras import Sequential from tensorflow.keras.layers import Input, Reshape, Dense EPOCHS = 10 from tensorflow.keras.datasets import cifar100 (training_dataset_x, training_dataset_y), (test_dataset_x, test_dataset_y) = cifar100.load_data() 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) 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' ] model = Sequential(name='ResNet121-Cifar-100') model.add(Input((32, 32, 3), name='Input')) model.add(DenseNet121(include_top=False, weights='imagenet', input_shape=(32, 32, 3), name='DenseModelTest')) model.add(Reshape((-1, ))) model.add(Dense(128, activation='relu', name='Dense-1')) model.add(Dense(128, activation='relu', name='Dense-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) 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(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 glob 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}') #---------------------------------------------------------------------------------------------------------------------------- Şimdi de DenseNet121 sınıfının fonksiyonel bir modelde nasıl kullanılacağı üzerinde duralım. Önceki konularda da bellirttiğimiz gibi uygulamacılar aslında öncedne hazırlanmış bu modelleri genellikle fonksiyonel model içerisinde kullanmaktadır. Fonksiyonel modelde anımsayacağınız gibi sürekli çıktı girdiye verilerek katmanlar oluşturuluyordu: inputs = Input((32, 32, 3), name='Input') x = DenseNet121(include_top=False, weights='imagenet', input_shape=(32, 32, 3), name='DenseModelTest')(inputs) x = Reshape((-1, ))(x) x = Dense(128, activation='relu', name='Dense-1')(x) x = Dense(128, activation='relu', name='Dense-2')(x) outputs = Dense(100, activation='softmax', name='Output')(x) model = Model(inputs, outputs) Burada Model nesnesinin kullanıldığına dikkat ediniz. Anımsanacağı gibi Model nesnesi oluşturulurken ona girdi ve çıktı katmanlarının verilmesi gerekiyordu. Geri kalan işlemler artık daha önce yaptığımız gibi devam ettirilebilir. Aşağıda aynı ResNet121 CIFAR örneğinin fonksiyonel modelle gerçekleştirime ilişkin örnek verilmiştir. #---------------------------------------------------------------------------------------------------------------------------- from tensorflow.keras.applications.densenet import DenseNet121 from tensorflow.keras import Model from tensorflow.keras.layers import Input, Reshape, Dense EPOCHS = 10 from tensorflow.keras.datasets import cifar100 (training_dataset_x, training_dataset_y), (test_dataset_x, test_dataset_y) = cifar100.load_data() 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) 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' ] inputs = Input((32, 32, 3), name='Input') x = DenseNet121(include_top=False, weights='imagenet', input_shape=(32, 32, 3), name='DenseModelTest')(inputs) x = Reshape((-1, ))(x) x = Dense(128, activation='relu', name='Dense-1')(x) x = Dense(128, activation='relu', name='Dense-2')(x) outputs = Dense(100, activation='softmax', name='Output')(x) model = Model(inputs, outputs) 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) 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(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 glob 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}') #---------------------------------------------------------------------------------------------------------------------------- Şimdi de başkaları tarafından hazırlanmış ve eğitilmiş olan modellerin kulanılmasına ilişkin örnekler verelim. Biz daha önce de bu amaçla çeşitli toplulukların oluşturuldupundan bahsetmiştik. Bunların en bilineni Kaggle denilen topluluktur. Ancak çeşitli framework'ler de "hub" adı altında Kaggle benzeri topluluklar oluşturmuştur. Örneğin Tensorflow için kişilerin modellerini paylaşabileceği "tensorflow hub" denilen bir topluluk vardır. Fakat daha önceden de belirttiğimiz gibi bu topluluk kendi sitesini kapatıp Kaggle'a geçmiştir. Bu tür topluluklardaki kişiler modellerini daha önce ele aldığımız formatlarda sunmaktadır. Ancak bu formattaki modellerde bazı sorunlar da ortaya çıkabilmektedir. Keras için önemli sorunalardan biri zamanında save edilmiş modellerin güncel versiyonlarla uyuşmamasından kaynaklanan sorunlardır. Keras işin başında Tensorflow'dan bağımsız ve birden fazla backend'i destekleyecek biçimde tasarlanmıştı. Sonraları Tensorflow ekibi bu Keras arayüzünü bünyesine kattı ve yeniden Tensorflow ile bağlantılı bir biçimde Keras'ı yazdı. Böylece biribirine benzeyen iki farklı Keras gerçekleştirimi ortaya çıktı. Ancak daha sonraları eski Keras ekibi Tensorflow desteğini kuvvetlendirdi. Tensorflow da bu yeni Keras ile (Keras'ın 3'lü versiyonları) kendisini senkronize etmeye başladı. Yani son yıllarda Tensorflow'daki Keras kütüphanesi orijinal Keras kütüphanesi ile aynı duruma gelmiştir. Ancak bir süre önce durum böyle değildi. İşte bu süreç içerisinde Tensorflow Keras ile oluşturulup save edilmiş modeller yeni Tensorflow Keras'ta çalışmaz hale gelebilmektedir. Ancak Tensorflow'daki orijinal Keras ile senkronize edilmiş yeni Keras'ın yanı sıra eski Keras'ı da muhafaza etmektedir. Bu eski Keras'a tf_keras ismiyle erişilebilmektedir. Bu eski Keras paketini aşağıdaki gibi yükleyebilirsiniz: pip install tf_keras Bu eski Keras'la yeni Keras'ın kullanımı büyük ölçüde aynıdır. Biz eski Keras'ı kullanacaksak tf_keras ismiyle, yeni Keras'ı kullanacaksak tensorflow.keras ismiyle import uygulamalıyız. #---------------------------------------------------------------------------------------------------------------------------- #---------------------------------------------------------------------------------------------------------------------------- Tensorflow Hub içerisine yerleştirilmiş olan modeller için bir URL'de oluşturulmaktadır. Bu modellerin doğrudan URL eşliğinde yüklenenip bir katman nesnesi haline getirilmesi için KerasLayer isimli bir sınıf bulundurulmuştur. Bu sınıfın __init__ metodunun parametrik yapısı şöyledir: hub.KerasLayer( handle, trainable=False, arguments=None, _sentinel=None, tags=None, signature=None, signature_outputs_as_dict=None, output_key=None, output_shape=None, load_options=None, **kwargs ) Buradaki en önemli iki parametre handle ve trainable parametreleridir. handle parametresinde modelin URL'si girilmektedir. trainable parametresi de modelin katmanlarının eğitime dahil edilip edilmeyeceğini belirtmektedir. Tabii yukarıda da belirttiğimiz gibi Tensorflow Hub artık tümden Kaggle'a taşınmış durumdadır. Biz söz konusu modelleri bu yöntemle kullanmak yerine model dosyasını Kaggle'dan indirip load_model fonksiyonuyla da yükleyebiliriz. Tensorflow Hub'ı modelleri kolay yüklemek amacıyla kullanabilmek için ona özgü olan kütüphaneyi de yüklememiz gerekir. Yukarıda açıkladığımız KerasLayer sınıfı da aslında bu kütüphane içerisindedir. Kütüphaneyi şöyle yükleyebiliriz: pip install tensorflow_hub #---------------------------------------------------------------------------------------------------------------------------- #---------------------------------------------------------------------------------------------------------------------------- 78. Ders - 10/11/2024 - Pazar #---------------------------------------------------------------------------------------------------------------------------- #---------------------------------------------------------------------------------------------------------------------------- Dersin başında geçmişe dönülerek Dataset nesnesi veren bazı hazır fonksiyonlar gözden geçirilmiştir. #---------------------------------------------------------------------------------------------------------------------------- #---------------------------------------------------------------------------------------------------------------------------- Aşağıda Tensorflow Hub'ta bulunan bir modelin kullanımına örnek verilmiştir. Ancak bu model Kesnorflow Keras'ın 2'li versiyonları için hazırlanmıştır. Bu nedenle bu model için biz tensorflow.keras yerine tf_keras kullandık. #---------------------------------------------------------------------------------------------------------------------------- from tf_keras.preprocessing import image_dataset_from_directory IMAGE_SIZE = (250, 250) training_dataset = image_dataset_from_directory(r'C:\Users\aslan\Downloads\flower_photos', label_mode='categorical', subset='training', seed=123, validation_split=0.2, image_size=IMAGE_SIZE, batch_size=32) validation_dataset = image_dataset_from_directory(r'C:\Users\aslan\Downloads\flower_photos', label_mode='categorical', subset='validation', seed=123, validation_split=0.2, image_size=IMAGE_SIZE, batch_size=32) from tensorflow_hub import KerasLayer keras_layer = KerasLayer('https://tfhub.dev/google/imagenet/efficientnet_v2_imagenet21k_xl/feature_vector/2') from tf_keras import Sequential from tf_keras.layers import Input, Rescaling, Dense model = Sequential(name='Tensorflow-Hub-EfficentNet') model.add(Input(IMAGE_SIZE + (3, ), name='Input')) model.add(Rescaling(1. / 255, name='Rescaling')) model.add(keras_layer) model.add(Dense(5, activation='softmax', name='Output')) model.compile('rmsprop', loss='categorical_crossentropy', metrics=['categorical_accuracy']) hist = model.fit(training_dataset, validation_data=validation_dataset, epochs=100) 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() #---------------------------------------------------------------------------------------------------------------------------- Makine öğrenmesi uygulamalarında kullanılan en önemli araçlardan biri de "Otomatik Makine Öğrenmesi (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ığı tipik işlemler şunlardır: - Özellik seçimi (feature selection) - Özelliklerin indirgenmesi (dimensionality feature reduction) - Verilerin ölçeklendirilmesi (feature scaling) - Kategorik verilerin sayısallaştırılması (label encoding, 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ı yapamamaktadı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 üst (hyper) 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ındaki parametrelerin nasıl ayarlanacağı uygulamacı tarafından deneme yanılma yöntemleriyle tespit edilmektedir. Bu tür araçlar bu sıkı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 makine öğrenmesi yöntemlerini 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. Otomatik makine öğrenmesi araçları süreci çok kolaylaştırmaktadır. Ancak her türlü yüksek düzeydeki araçlarda olduğu gibi ince işlemler bu araçların çoğuyla yapılamamaktadır. Tabii uygulamacı bu araçları kullanıp buradan elde edilen modellerden faydalanabilir. Yani bu araçları model keşfetmek için de kullanabilir. Auto ML araçlarından bazıları tüm süreci otomatize ederken bazı unsurların kullanıcı tarafından belirlenmesine de olanak sağlamaktadır. Ancak bu konuda Auto ML araçları arasında önemli farklılıklar bulunabilmektedir. #---------------------------------------------------------------------------------------------------------------------------- #---------------------------------------------------------------------------------------------------------------------------- Yapay sinir ağları için kullanılan Auto ML araçlarının en yaygın kullanılanı "AutoKeras" isimli araçtır. Bunun yanı sıra "H2O" ve Cloud sistemlerine entegre edilmiş araçlar da sık kullanılanlar arasındadır. Biz bu bölümde AutoKeras aracının kullanımını ele alacağız. #---------------------------------------------------------------------------------------------------------------------------- #---------------------------------------------------------------------------------------------------------------------------- 79. Ders - 16/11/2024 - Cumartesi #---------------------------------------------------------------------------------------------------------------------------- #---------------------------------------------------------------------------------------------------------------------------- AutoKeras yapay sinir ağlarını kullanarak kestirim işlemlerini otomatize eden bir araçtır. İsminden de anlaşılacağı gibi bu araç neticede bir Keras modeli oluşturmaktadır. AutoKeras aracının resmi sitesi şöyledir: https://autokeras.com/ Bu sitede oldukça basit örneklerle aracın nasıl kullanılacağı açıklanmıştır. Bu konuda yazılmış olan "Automated Machine Learning with AutoKeras (Luis Sobrecueva)" isimli bir kitap da bulunmaktadı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 Ancak AutoKeras'ın install edilen tensorflow versiyonu ile uyumlu olması gerekmektedir. Kursun yapıldığı sırada AutoKeras'ın son versiyonu 2.0.0 versiyonudur. Ancak bu versiyon maalesef Windows'ta install edilememektedir. Fakat macOS ve Linux sistemlerinde sorunsuz bir biçimde install edilebilmeektedir. Windows'ta autokeras install edilmeye çalışıldığında 2.0.0 versiyonu değil 1.0.20 versiyonu install edilebilmektedir. Bu versiyon da maalesef tensorflow'un eski versiyonları kullanılarak yazılmıştır. Bu nedenle Windows sistemlerinde tensorflow kütüphanesinin de "downgrade" edilmesi gerekir. AutoKeras'ın 1.0.20 versiyonunun çalışabilmesi için gereksinim duyulan kütüphanelerin versiyon numaraları şöyledir: python==3.8.15 tensorflow==2.10.0 numpy==1.24.24 Windows'ta bu çalışma ortamını kolay hazırlamak için Anaconda'da "Envirionments/Create" yapıp Python versiyonunu 3.8.X olarak ayarlayıp sanal bir ortam oluşturabilirsiniz. Sonra bu sanal ortamda "Open Terminal" yapıp "conda-forge" kullanarak aşağıdaki gibi kurulumu yapabilirsiniz: conda install autokeras --channel conda-forge #---------------------------------------------------------------------------------------------------------------------------- #---------------------------------------------------------------------------------------------------------------------------- AutoKeras kütüphanesinde yüksek seviyeli 6 temel sınıf vardır: ImageClassifier ImageRegressor TextClassifier TextRegressor StructuredDataClassifier (AutoKeras 2'de kaldırıldı) StructuredDataRegressor (AutoKeras 2'de kaldırıldı) 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 kişinin yaşını tespit etme problemi gibi), TextClassifier sınıfı yazıları sınıflandırmak için, TextRegressor 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ışma gibi), 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 sınıfı da farklı türlere ilişkin sütunlara sahip regresyon problemleri için kullanılmaktadır. Ancak bu sınıflar AutoKeras 2'de kaldırılmıştı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. AutoKeras 2 ile birlikte kütüphane üzerinde önemli değişiklikler yapılmıştır. Kütüphaneye pek çok Block sınıfı ve daha genel Input sınıfları eklenmiştir. Biz önce bu temel sınıfları göreceğiz sonra AutoKeras 2 ile birlikte eklenen bu yeniş sınıfları göreceğiz. #---------------------------------------------------------------------------------------------------------------------------- #---------------------------------------------------------------------------------------------------------------------------- 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 belirlenmektedir. Bu belirleme training_dataset_y içerisindeki farklı değerlerin sayısı ile yapı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 var olsa bile yeni değerleri onun üzerine yazazağı anlamına gelmektedir. overwrite paramtresi True geçildiğinde daha önce denenmiş ve saklanmış olan modeller doğrudan kullanılmaktadır. Metrik değerler metrics parametresiyle verilebilmektedir. Örneğin: import autokeras as ak ic = ak.ImageClassifier(max_trials=5, overwrite=True) ImageClassifier sınıfının kendi içerisinde pretrained verileri kullanıp kullanmadığı konusunda dokğmanlarda bir bilgi yoktur. Bazı kaynaklar ImageClassifer 2) Modelin derlenmesi işlemi uygulamacı tarafından yapılmaz. Dolayısıyla uygulamacı doğrudan fit işlemi yapar. Buradaki fit metodunun kullanımı tamamen Keras'taki fit metodu gibidir. fit metoduna uygulamacı 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 metoduna vereceğimiz resimlerin üç boyutlu bir matris biçiminde olması gerekir. Yani RGB resimler için matris boyutu (width, height, 3) biçiminde gri tonlamalı resimler için (width, height, 1) biçiminde olmalıdır. Tabii fit metodu parçalı eğtim de yapabilmektedir. Yani biz bu metodun birinci parametresine üretici fonksiyonları ya da Dataset nesnelerini geçirebiliriz. fit işlemi sonucunda Keras'ta olduğu gibi bir History callback nesnesi elde edilmektedir. Tabii programcı fit metoduna istediği callback nesnelerini callbacks parametresi yoluyla geçirebilir. fit metodununb parametrik yapısı şöyledir: ImageClassifier.fit(x=None, y=None, epochs=None, callbacks=None, validation_split=0.2, validation_data=None, **kwargs) Örneğin: ic = ak.ImageClassifier(max_trials=5, metrics=['categorical_accuracy'], overwrite=True) #---------------------------------------------------------------------------------------------------------------------------- 80. Ders - 17/11/2024 - Pazar #---------------------------------------------------------------------------------------------------------------------------- 3) En iyi model AutoKeras tarafından bulunduktan sonra model test edilmelidir. Yine modelin testi için ImageClassifier sınıfının evaluate metodu kullanılmaktadır. evaluate metodu Keras'taki Sequential sınıfının evaluate metodu gibi kullanılmaktadır. Metodun parametrik yapısı şöyledir: ImageClassifier.evaluate(x, y=None, batch_size=32, verbose=1, **kwargs) Örneğin: 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]}') 4) Seçilen en iyi modelin test işleminden sonra artık kestirim işlemleri yapılabilir. Bunun için ImageClassifier sınıfının predict metodu kullanılmaktadır. predict metodu da tamamen Sequential sınıfının predict metodu gibidir. Parametrik yapısı şöyledir: ImageClassifier.predict(x, batch_size=32, verbose=1, **kwargs) Örneğin: predcit_result = ic.tredict(predict_dataset_x, predict_dataset_y) 5) 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. model = ic.export_model() 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, model bilgileri ve kalınan kalınan yer not alınmaktadır. 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 denenen model belirlediğimiz patience değerine bağlı olarak erken sonlandırılabilecektir. 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 ) Metodun output_dim parametresi çıktı katmanının kaç değişkenden oluşacağını belirtir. Bu parametre için argüman girilemzse çıktı katmanındaki değişken sayısı y verilerinden otomatik olarak elde edilmektedir. Yine bu sınıf da özellik seçimi, özellik ölçeklemesi, one-hot-encoding gibi ön işlemleri kendisi yapmaktadır. 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]}') #---------------------------------------------------------------------------------------------------------------------------- AutoKeras'ın 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ı şöyledir: autokeras.TextClassifier( num_classes=None, multi_label=False, loss=None, metrics=None, project_name="text_classifier", max_trials=100, directory=None, objective="val_loss", tuner=None, overwrite=False, seed=None, max_model_size=None, **kwargs ) Metodun parametrik yapısı ImageClassifier sınıfının __init__ metoduna çok benzemektedir. Kullanımı da benzerdir. TextClassifier sınıfının fit metodunda training_dataset_x yazılardan oluşan bir NumPy dizisi ya da Dataset nesnesi olabilir. training_dataset_y de kategorik değerlere ilişkin bir NumPy dizisi olabilir ya da sayısallaştırılmış kategorik değerlerden oluşabilir. AutoKeras yazının parse edilmesi, vektörel hale getirilmesi, word embedding gibi işlemleri kendisi yapmaktadır. Yani uygulamacının yalnızca yazıları fit metoduna vermesi yeterlidir. 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 Mails" veri kümesidir. Bu veri kümesinde birtakım e-postalar ve onların spam olup olmadığı bilgileri vardır. Böylece e-postalara ilişkin spam filtreleri 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 yapabiliriz. "label" isimli sütun "spam" ya da "ham" değerlerinden oluşmaktadır. label_num aynı değerlerin 0 ve 1 ile sayısallaştırılmış halini içermektedir. E-postalatın içeriği 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, metrics=['binary_accuracy']) 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() 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]}') #---------------------------------------------------------------------------------------------------------------------------- TextRegressor sınıfı bir yazdıdan sayısal bir değer kstirmek için kullanılmaktadır. Sınıfın __init__ metodunun parametrik yapısı şöyledir: autokeras.TextRegressor( output_dim=None, loss="mean_squared_error", metrics=None, project_name="text_regressor", max_trials=100, directory=None, objective="val_loss", tuner=None, overwrite=False, seed=None, max_model_size=None, **kwargs Sınıfın kullanımı TextClassifier sınıfına oldukça benzemektedir. #---------------------------------------------------------------------------------------------------------------------------- #---------------------------------------------------------------------------------------------------------------------------- 81. Ders 23/11/2024 - Cumartesi #---------------------------------------------------------------------------------------------------------------------------- #---------------------------------------------------------------------------------------------------------------------------- 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ürü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. AutoKeras'ın 2'li versiyonlarıyla birlikte izleyen paragraflarda ele alacağımız yeni birtakım sınıflar eklenmiştir. Proje ekibi bu yeni sınıfların kullanılmasını teşvik etmek amacıyla bu sınıfı tamamen AutoKeras'tan kaldırmıştır. Yani eğer siz kütüphanenin 2'li versiyonlarını kullanıyorsanız bu sınıf kütüphanenizde bulunmayacaktır. Ancak biz burada yine sınıf hakkında bilgiler vereceğiz. Bu sınıfın bir uygulaması olarak Titanik veri kümesini kullanacağız. Titanik veri 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. Titanik veri kümesi aşağıdaki bağlantıdan indirilebilir: https://www.kaggle.com/datasets/yasserh/titanic-dataset Veri kğmesinin görünümü aşağıdaki gibidir: PassengerId,Survived,Pclass,Name,Sex,Age,SibSp,Parch,Ticket,Fare,Cabin,Embarked 1,0,3,"Braund, Mr. Owen Harris",male,22,1,0,A/5 21171,7.25,,S 2,1,1,"Cumings, Mrs. John Bradley (Florence Briggs Thayer)",female,38,1,0,PC 17599,71.2833,C85,C 3,1,3,"Heikkinen, Miss. Laina",female,26,0,0,STON/O2. 3101282,7.925,,S 4,1,1,"Futrelle, Mrs. Jacques Heath (Lily May Peel)",female,35,1,0,113803,53.1,C123,S 5,0,3,"Allen, Mr. William Henry",male,35,0,0,373450,8.05,,S 6,0,3,"Moran, Mr. James",male,,0,0,330877,8.4583,,Q 7,0,1,"McCarthy, Mr. Timothy J",male,54,0,0,17463,51.8625,E46,S 8,0,3,"Palsson, Master. Gosta Leonard",male,2,3,1,349909,21.075,,S 9,1,3,"Johnson, Mrs. Oscar W (Elisabeth Vilhelmina Berg)",female,27,0,2,347742,11.1333,,S 10,1,2,"Nasser, Mrs. Nicholas (Adele Achem)",female,14,1,0,237736,30.0708,,C .......... 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]}') #---------------------------------------------------------------------------------------------------------------------------- StructuredDataRegressor 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 ) Ancak yukarıda da belirttiğimiz gibi kütüphanenin 2'li versyionlarıyla birlikte bu sınıf da kütüphaneden kaldırılmıştır. Aşağıda Boston Housing Prices veri kümesi üzerinde StructuredDataRegresoor sınıfının kullanımına bir örnek verilmiştir. #---------------------------------------------------------------------------------------------------------------------------- import pandas as pd df = pd.read_csv('housing.csv', delimiter=r'\s+', header=None) dataset_x = df.iloc[:, :-1].to_numpy(dtype='float32') dataset_y = df.iloc[:, -1].to_numpy(dtype='float32') from sklearn.model_selection import train_test_split training_dataset_x, test_dataset_x, training_dataset_y, test_dataset_y = train_test_split(dataset_x, dataset_y, test_size=0.2) import autokeras as ak sdr = ak.StructuredDataRegressor(max_trials=10, overwrite=True, metrics=['mae']) from tensorflow.keras.callbacks import EarlyStopping esc = EarlyStopping(patience=5, restore_best_weights=True) hist = sdr.fit(training_dataset_x, training_dataset_y, epochs=100, 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.plot(hist.epoch, hist.history['loss']) plt.legend(['Loss', 'Validation Loss']) plt.show() model = sdr.export_model() eval_result = sdr.evaluate(test_dataset_x, test_dataset_y) for i in range(len(eval_result)): print(f'{model.metrics_names[i]}: {eval_result[i]}') import numpy as np predict_data = np.array([[0.11747, 12.50, 7.870, 0, 0.5240, 6.0090, 82.90, 6.2267, 5, 311.0, 15.20, 396.90, 13.27]]) predict_result = model.predict(predict_data) for val in predict_result[:, 0]: print(val) #---------------------------------------------------------------------------------------------------------------------------- #---------------------------------------------------------------------------------------------------------------------------- Yukarıda da belirttiğimiz gibi AutoKeras 2 ile birlikte kütüphane üzerinde önemli değişiklikler yapılmıştır. Örneğin kütüphaneden StructuredDataClassifier ve StructuredDataRegressor sınıfları tamamen kaldırılmıştır ve kütüphaneye pek çok Block sınıfı eklenmiştir. AutoKeras 2'de üç çeşit Input sınıfı bulunmaktadır: Input TextInput ImageInput Input sınıfı sütunlara sahip klasik "tablo biçimindeki (tabular)" veri kümeleri için kullanılmaktadır. TextInput sınıfı ismi üzerinde metinsel girdiler için ImageInput sınıfı ise resimsel girdiler için bulundurulmuştur. Eğer girdi için Input sınıfı kullanılacaksa fit işlemi sırasında fit metoduna verilen x verilerinin hepsinin nümerik olması gerekmektedir. Önişlemler AutoModel sınıfı tarafından yapılmaktadır. Dolayısıyla bizim kategorik veriler için one-hot-encoding yapmamıza gerek yoktur. Ancak kategorik verileri bizim sayısal hal getirmemiz gerekir. Bu sınıflar AutoModel sınıfına girdi yapılmaktadır. AutoModel sınıfının __init__ metodunun parametrik yapısı şöyledir: autokeras.AutoModel( inputs, outputs, project_name="auto_model", max_trials=100, directory=None, objective="val_loss", tuner="greedy", overwrite=False, seed=None, max_model_size=None, **kwargs ) Metodun inputs parametresine yukarıdaki Input sınıfları türünden nesneler argüman olarak verilmektedir. outputs parametresine ise aşağıdaki iki sınıf türünden nesneler girilmelidir. ClassificationHead RegressionHead Modelin metrics parametresi ClassificationHead ve RegressionHead sınıflarında belirtilmektedir. Yine metodun max_trials parametresi kaç modelin deneneceğini belirtmektedir. Diğer parametreler daha önce görmüş olduğumuz sınıfların parametrelerine benzerdir. Örneğin: import autokeras as ak inp = ak.Input() out = ak.ClassificationHead() auto_model = ak.AutoModel(inputs=inp, outputs=out, max_trials=20, overwrite=True) Bu biçimde AutoModel nesnesi oluşturulduktan sonra artık AutoModel sınıfının fit metodutla eğitim, evalute metoduyla test işlemi ve predict metoduyla da kestieim işlemi yapılabilir. Örneğin: hist = auto_model.fit(training_dataset_x, training_dataset_y, validation_split=0.2, epochs=50) ... eval_result = auto_model.evaluate(test_dataset_x, test_dataset_y) ... predict_result = auto_model.predict(predict_dataset_x) #---------------------------------------------------------------------------------------------------------------------------- #---------------------------------------------------------------------------------------------------------------------------- 82. Ders 24/11/2024 - Pazar #---------------------------------------------------------------------------------------------------------------------------- #---------------------------------------------------------------------------------------------------------------------------- Şimdi de Titanik veri kümesi üzerinde AutoKeras 2'deki AutoModel kullanımına bir örnek verelim. AutoModel kullanırken resim ve yazılardan oluşmayan klasik tablo biçiminde veri kümelerinde girdi katmanı için Input sınıfı kullanılmalıdır. Ancak Input sınıfı için girdiler verilirken tüm sütunların nümerik biçimde olması gerekmektedir. One-hot-encoding gibi işlemleri AutoKeras kendisi yapıyor olsa da kategorik sütunlar LabelEncoder gibi bir sınıfla sayısal biçime dönüştürülmelidir. Ayrıca AutoKeras 2'de Input katmanı için girdilerde hiç eksik veri ve NaN verinin olmaması gerekir. Yani Imputation ugulamacı tarafından uygulanmalıdır. Biz Tintaic veri kümesi için aşağıdaki hazırlıkları yapabiliriz: import pandas as pd df = pd.read_csv('Titanic-Dataset.csv') dataset_y = df['Survived'].to_numpy('uint8') df = df.drop(['Survived', 'PassengerId', 'Cabin', 'Name', 'Parch', 'Ticket'], axis=1) from sklearn.preprocessing import LabelEncoder le = LabelEncoder() df['Sex'] = le.fit_transform(df['Sex']) df['Embarked'] = le.fit_transform(df['Embarked']) from sklearn.impute import SimpleImputer si = SimpleImputer(strategy='mean') df['Age'] = si.fit_transform(df[['Age']]) dataset_x = df.to_numpy('float32') Burada bazı gereksiz sütunlar atılmıştır. Bazı kategorik sütunlar LabelEncoder ile sayısal biçime dönüştürülmüştür. Yaş belirten Age sütununda eksik veri olduğu için bu sütun üzerinde imputation uygulanmıştır. Bundan sonra veri kümesini eğitim ve test biçiminde bölebiliriz: 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 AutoModel nesnesini oluşturabiliriz: inp = ak.Input() out = ak.ClassificationHead() auto_model = ak.AutoModel(inputs=inp, outputs=out, max_trials=20, overwrite=True) Aşağıda örnek bir bütün olarak vrilmiştir. Burada elde edilen en iyi modelin Keras katman yapısı şöyledir: Model: "functional" ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━┓ ┃ Layer (type) ┃ Output Shape ┃ Param # ┃ ┡━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━┩ │ input_layer (InputLayer) │ (None, 6) │ 0 │ ├─────────────────────────────────┼────────────────────────┼───────────────┤ │ cast_to_float32 (CastToFloat32) │ (None, 6) │ 0 │ ├─────────────────────────────────┼────────────────────────┼───────────────┤ │ dense (Dense) │ (None, 256) │ 1,792 │ ├─────────────────────────────────┼────────────────────────┼───────────────┤ │ batch_normalization │ (None, 256) │ 1,024 │ │ (BatchNormalization) │ │ │ ├─────────────────────────────────┼────────────────────────┼───────────────┤ │ re_lu (ReLU) │ (None, 256) │ 0 │ ├─────────────────────────────────┼────────────────────────┼───────────────┤ │ dropout (Dropout) │ (None, 256) │ 0 │ ├─────────────────────────────────┼────────────────────────┼───────────────┤ │ dense_1 (Dense) │ (None, 256) │ 65,792 │ ├─────────────────────────────────┼────────────────────────┼───────────────┤ │ batch_normalization_1 │ (None, 256) │ 1,024 │ │ (BatchNormalization) │ │ │ ├─────────────────────────────────┼────────────────────────┼───────────────┤ │ re_lu_1 (ReLU) │ (None, 256) │ 0 │ ├─────────────────────────────────┼────────────────────────┼───────────────┤ │ dropout_1 (Dropout) │ (None, 256) │ 0 │ ├─────────────────────────────────┼────────────────────────┼───────────────┤ │ dropout_2 (Dropout) │ (None, 256) │ 0 │ ├─────────────────────────────────┼────────────────────────┼───────────────┤ │ dense_2 (Dense) │ (None, 1) │ 257 │ ├─────────────────────────────────┼────────────────────────┼───────────────┤ │ classification_head_1 │ (None, 1) │ 0 │ │ (Activation) │ │ │ └─────────────────────────────────┴────────────────────────┴───────────────┘ Total params: 69,889 (273.00 KB) Trainable params: 68,865 (269.00 KB) Non-trainable params: 1,024 (4.00 KB) #---------------------------------------------------------------------------------------------------------------------------- import pandas as pd df = pd.read_csv('Titanic-Dataset.csv') dataset_y = df['Survived'].to_numpy('uint8') df = df.drop(['Survived', 'PassengerId', 'Cabin', 'Name', 'Parch', 'Ticket'], axis=1) from sklearn.preprocessing import LabelEncoder le = LabelEncoder() df['Sex'] = le.fit_transform(df['Sex']) df['Embarked'] = le.fit_transform(df['Embarked']) from sklearn.impute import SimpleImputer si = SimpleImputer(strategy='mean') df['Age'] = si.fit_transform(df[['Age']]) dataset_x = df.to_numpy('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) import autokeras as ak inp = ak.Input() out = ak.ClassificationHead() auto_model = ak.AutoModel(inputs=inp, outputs=out, max_trials=20, overwrite=True) hist = auto_model.fit(training_dataset_x, training_dataset_y, validation_split=0.2, epochs=50) keras_model = auto_model.export_model() keras_model.summary() 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() # evaluation eval_result = auto_model.evaluate(test_dataset_x, test_dataset_y) for i in range(len(eval_result)): print(f'{keras_model.metrics_names[i]}: {eval_result[i]}') # prediction df_predict = pd.read_csv('predict.csv') df_predict = df_predict.drop(['PassengerId', 'Cabin', 'Name', 'Parch', 'Ticket'], axis=1) le = LabelEncoder() df_predict['Sex'] = le.fit_transform(df_predict['Sex']) df_predict['Embarked'] = le.fit_transform(df_predict['Embarked']) predict_dataset_x = df_predict.to_numpy('float32') predict_result = auto_model.predict(predict_dataset_x) print(predict_result) #---------------------------------------------------------------------------------------------------------------------------- Biz yukarıdaki örnekte AutoModel'in girdi ve çıktı katmanlarını birbirinden bağımsız biçimde şöyle oluştuduk: inp = ak.Input() out = ak.ClassificationHead() auto_model = ak.AutoModel(inputs=inp, outputs=out, max_trials=20, overwrite=True) Bu biçimde bir AutoModel oluşturduğumuzda tüm ara katmanlar, ara katmanlardaki nöron sayıları AutoKeras tarafından oluşturulmaktadır. Ancak biz aslında AutoModel'lerde ara katmanlar üzerinde belirlemeler de de bulunabilmekteyiz. AutoKeras'ın 2'li versiyonlarıyla eklenen AutoModel sistemi fonksiyonel bir kullanıma sahiptir. Yani AutoModel bizim Keras'ta gördüğümüz fonksiyonel model gibi tasarlanmıştır. AutoModel'deki fonkisyonel kullanımda katman nesneleri birbirilerine verilerek eklenebilmektedir. Örneğin: inp = ak.Input(...) x = ak.DenseBlock(...)(inp) x = ak.DenseBlock(...)(x) x = ak.DenseBlock(...)(x) output = ak.ClassificationHead(...)(x) auto_model = ak.AutoModel(inputs=inp, outputs=out) Burada önce bir Input nesnesi oluşturulmuş sonra DenseBlock nesneleri bunun üzerine eklenmiş ve nihayetinde bir çıktı nesnesi elde edilmiştir. Buradaki kullanım biçimi Keras'ta görmüş olduğumuz fonksiyonel modele çok benzemektedir. Bu tüm ara katmanlar ve hypper parametreler AutoKeras tarafından elde oluşturulmaktadır. AutoKeras'ın fonksiyonel kullanımında uygulamacı katmanların neler olacağını ve kaç tane olacağını ana hatlarıyla kendisi oluşturmaktadır. Katmanlar Block denilen sınıflarla temsil edilmiştir. Block sınıfları şunlardır: ConvBlock DenseBlock ResNetBlock RNNBlock XceptionBlock ImageBlock TextBlock Bu Block sınıflarının yanı sıra ayrıca yardımcı birkaç sınıf da vardır: Normalization Merge SpatialReduction TemporalReduction Normalization AutoKeras'ın Block sınıfları ile model oluşturulduğunda özellik ölçeklemesi otomatik yapılmamaktadır. Özellik ölçeklemesinin yapılması için Normailzation katmanının kullanılması gerekmektedir. DenseBlock AutoKeras'a şunları söylemektedir: "Modele bir ya da birden fazla Dense katman ekleyebilirsin, bunların nöron sayılarını ve aktivasyon fonksiyonlarını sen ayarlayabilirsin". Örneğin: inp = ak.Input(...) x = ak.Normaliation()(inp) x = ak.DenseBlock()(x) out = ak.ClassificationHead()(x) auto_model = ak.AutoModel(inputs=inp, outputs=out) Burada biz AutoKeras'a girdi katmanından sonra istenildiği kadar Dense katman kullabileceğini söylemekteyiz. Ancak biz istersek DenseBlock sınıfında hangi sayıda Dense katmanın kullanılacağını metodun num_layers parametresiyle belirleyebiliriz. Örneğin: inp = ak.Input(...) x = ak.Normaliation()(inp) x = ak.DenseBlock(num_layers=2)(inp) out = ak.ClassificationHead()(x) auto_model = ak.AutoModel(inputs=inp, outputs=out) Burada artık girdi katmanından sonra AutoKeras kesinlikle iki tane Dense katman kullanacaktır. Ancak bu katmanın nöron sayılarını ve diğer özelliklerini kendisi belirleyecektir. Biz DenseBlock ile eklenecek olan Dense katmanlardaki nöron sayılarını da num_units parametresiyle belirleyebilmekteyiz. Örneğin: inp = ak.Input(...) x = ak.DenseBlock(num_layers=2, num_units=32)(inp) out = ak.ClassificationHead()(x) auto_model = ak.AutoModel(inputs=inp, outputs=out) Artık burada iki tane Dense katman kullanılacak ve bu katmanların nöron sayıları 32 olacaktır. Aşağıdaki örnekte Titanic veri kümesinde fonksiyonel DenseBlock kullanımına örnek verilmiştir. Kodun AutoKeras'ı ilgilendiren kısmı şöyledir: import autokeras as ak inp = ak.Input() x = ak.Normaliation()(x) x = ak.DenseBlock()(inp) out = ak.ClassificationHead()(x) auto_model = ak.AutoModel(inputs=inp, outputs=out, max_trials=100, overwrite=True) #---------------------------------------------------------------------------------------------------------------------------- import pandas as pd df = pd.read_csv('Titanic-Dataset.csv') dataset_y = df['Survived'].to_numpy('uint8') df = df.drop(['Survived', 'PassengerId', 'Cabin', 'Name', 'Parch', 'Ticket'], axis=1) from sklearn.preprocessing import LabelEncoder le = LabelEncoder() df['Sex'] = le.fit_transform(df['Sex']) df['Embarked'] = le.fit_transform(df['Embarked']) from sklearn.impute import SimpleImputer si = SimpleImputer(strategy='mean') df['Age'] = si.fit_transform(df[['Age']]) dataset_x = df.to_numpy('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) import autokeras as ak import autokeras as ak inp = ak.Input() x = ak.Normalization()(inp) x = ak.DenseBlock()(x) out = ak.ClassificationHead()(x) auto_model = ak.AutoModel(inputs=inp, outputs=out, max_trials=100, overwrite=True) hist = auto_model.fit(training_dataset_x, training_dataset_y, validation_split=0.2, epochs=50) keras_model = auto_model.export_model() keras_model.summary() 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() # evaluation eval_result = auto_model.evaluate(test_dataset_x, test_dataset_y) for i in range(len(eval_result)): print(f'{keras_model.metrics_names[i]}: {eval_result[i]}') # prediction df_predict = pd.read_csv('predict.csv') df_predict = df_predict.drop(['PassengerId', 'Cabin', 'Name', 'Parch', 'Ticket'], axis=1) le = LabelEncoder() df_predict['Sex'] = le.fit_transform(df_predict['Sex']) df_predict['Embarked'] = le.fit_transform(df_predict['Embarked']) predict_dataset_x = df_predict.to_numpy('float32') predict_result = auto_model.predict(predict_dataset_x) print(predict_result) #---------------------------------------------------------------------------------------------------------------------------- 83. Ders - 01/12/2024 - Pazar #---------------------------------------------------------------------------------------------------------------------------- #---------------------------------------------------------------------------------------------------------------------------- Şimdi de AutoKeras 2 ile AutoModel kullanarak evrişimli bir resim sınıflandırma örneği yapalım. AutoKeras 2'deki ConvBlock bir ya da birden fazla evrişim katmanını temsil etmektedir. Yani biz ImageInput nesnesinden sonra fonksiyonel modele ConvBlock nesnesini eklersek aslında modele bir ya da birden fazla evrişim katmanı eklemiş oluruz. Burada da eklenecek evrişim katmanlarının bazı özellikleri uygulamacı tarafından belirlenebilmektedir. ImageInput sınıfında girdiler gri tonlamalı resimler (samples, width, height) biçiminde de RGB resimler (samples, width, height, channels) biçiminde de girilebilir. Cifar-100 örneği için AutoModel şöyle oluşturulabilir: inp = ak.ImageInput() x = ak.Normalization()(inp) x = ak.ConvBlock()(x) x = ak.DenseBlock(num_layers=2)(x) out = ak.ClassificationHead()(x) auto_model = ak.AutoModel(inputs=inp, outputs=out, max_trials=1, overwrite=True) Aşağıda Cifar-100 örneğinin AutoModel kullanılarak gerçekleştirimine bir örnek verilmiştir. #---------------------------------------------------------------------------------------------------------------------------- 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) 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 autokeras as ak inp = ak.ImageInput() x = ak.Normalization()(inp) x = ak.ConvBlock()(x) x = ak.DenseBlock(num_layers=2)(x) out = ak.ClassificationHead(metrics=['categorical_accuracy'])(x) auto_model = ak.AutoModel(inputs=inp, outputs=out, max_trials=1, overwrite=True) hist = auto_model.fit(training_dataset_x, training_dataset_y, epochs=5) 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 = auto_model.evaluate(test_dataset_x, test_dataset_y) for i in range(len(eval_result)): print(f'{eval_result[i]}') # prediction import numpy as np import os count = 0 hit_count = 0 for path in glob.glob('Predict-Pictures-Cifar100/*.*'): image = plt.imread(path) scaled_image = image / 255 model_result = auto_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}') #---------------------------------------------------------------------------------------------------------------------------- Resim sınıflandırma işlemleri için ResNet modeli daha iyi sonuçlar vermektedir. AutoKeras'ta ResNetBlock sınıfı bu modeli otomatik kullanmak için bulundurulmuştur. Model içerisindeki katmanlar ve onların hyper parametreleri ResNetBlock tarafından otomatik ayarlanmaktadır. ResNetBock kullanımına tipik örnek şöyle verilebilir: inp = ak.ImageInput() x = ak.Normalization()(inp) x = ak.ResNetBlock()(x) x = ak.DenseBlock(num_layers=2)(x) out = ak.ClassificationHead()(x) auto_model = ak.AutoModel(inputs=inp, outputs=out, max_trials=1, overwrite=True) Aşağıda Cifar-100 örneğinin ResNetBlock kullanılarak gerçekleştirimine bir örnek verilmiştir. #---------------------------------------------------------------------------------------------------------------------------- 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) 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 autokeras as ak inp = ak.ImageInput() x = ak.Normalization()(inp) x = ak.ResNetBlock()(x) x = ak.DenseBlock(num_layers=2)(x) out = ak.ClassificationHead()(x) auto_model = ak.AutoModel(inputs=inp, outputs=out, max_trials=1, overwrite=True) hist = auto_model.fit(training_dataset_x, training_dataset_y, epochs=5) 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 = auto_model.evaluate(test_dataset_x, test_dataset_y) for i in range(len(eval_result)): print(f'{eval_result[i]}') # prediction import numpy as np import os count = 0 hit_count = 0 for path in glob.glob('Predict-Pictures-Cifar100/*.*'): image = plt.imread(path) scaled_image = image / 255 model_result = auto_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}') #---------------------------------------------------------------------------------------------------------------------------- Kursumuzun bu bölümünde geçici süre yapay sinir ağlarından uzaklaşacağız ve denetimsiz öğrenme (unsupervised learning) konusu üzerinde duracağız. #---------------------------------------------------------------------------------------------------------------------------- #---------------------------------------------------------------------------------------------------------------------------- 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. x ve y verileri arasında eğitim yoluyla bir ilişki kurmaya çalışan öğrenme yöntemlerine denetimli öğrenme yöntemleri denilmektedir. Yani denetimli öğrenmede bir eğitim süreci vardır. Ancak 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 model onun elma mı, armut mu, kayısı olduğunu bize söyler. Denetimsiz (unsupervised) modellerde ise 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ıkaladan hareketle bunları gruplayabilir. Dolayısıyla bu gruplama 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 da bir çeşit kestirimdir. Denetimsiz öğrenme için çeşitli yöntemler bulunmaktadır. Ancak denetimsiz öğrenmede kullanılan en önemli yüntem grubu "kümeleme (clustering)" denilen yöntem grubudur. Bu nedenle denetimsiz öğrenme denildiğinde akla ilk gelen yöntem grubu kümelemedir. Biz de önce kümeleme yöntemlerini ele alıp inceleyeceğ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)" bir 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ü olup olmadığına yönelik bir model oluşturmak isteyelim. Elimizde virüslü dosyalarla virüssüz 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 da denetimsiz öğrenme 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 da 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 istatistik bağlamında değil daha çok makine öğrenmesi ve veri bilimi 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 ediyorsa, iki satırın 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 kümesindeki satırlar n boyutlu uzayda bir nokta gibi düşünülebilir. Benzerlik de "uzaklık (distance)" temeline dayandırılabilmektedir. Eğer n boyutlu uzayda iki nokta arasındaki uzaklık düşük ise bu iki nokta benzer, yüksek ise bu iki nokta benzer değildir. Ancak uzaklık da aslında 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 özellikli (sütunlu) bir veri kümesindeki satırlar kartezyen koordinat sisteminde birer nokta belirtmektedir. Bu iki nokta arasındaki Öklit 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. Yani her boyutun 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 hazır bir fonksiyon yoktur. Ancak bunun için SciPy içerisinde scipy.spatial.distance modülünde euclidean isimli bir fonksiyon bulunmaktadır. from scipy.spatial.distance import euclidean a = np.array([1, 4, 6, 2]) b = np.array([4, 2, -1, 7]) dst = euclidean(a, b) #---------------------------------------------------------------------------------------------------------------------------- import numpy as np def euclidean_distance(x, y): return np.sqrt(np.sum((x - y) ** 2)) a = np.array([1, 1, 5, 6, 7, 8]) b = np.array([2, 2, 3, 6, 4, 6]) dist = euclidean_distance(a, b) print(dist) from scipy.spatial.distance import euclidean dist = euclidean(a, b) print(dist) #---------------------------------------------------------------------------------------------------------------------------- Ökltit uzaklığının dışında daha az kullanılıyor olsa da birkaç önemli uzaklık tanımı daha vardır. Manhattan uzaklığı (Manhattan distance) iki nokta arasındaki birbirine dik doğrularla gidilebilen uzaklıktır. (New York'taki Manhattan denilen bölge birbirlerini kesen büyük caddelerden oluşmaktadır. Bir yerden bir yere gitmek için bu cadde kesişimlerinden geçmek gerekir. Bu nedenle bu uzaklık tanımına "Manhattan" uzaklığı denmiştir.) a ve b noktalar, i'ise uzayın boyut indeksi olmak üzere Manhattan uzaklığı matematiksel olarak sum(abs(ai - bi)) biçiminde hesaplanmaktadır. Bu hesabı yapan fonksiyonu NumPy kullanılarak şöyle yazabiliriz: 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ığı için SciPy kütüphanesinde scipy.spatial.distance modülündeki cityblock isimli bir fonksiyon bulunmaktadır: from scipy.spatial.distance import cityblock mdist = cityblock(a, b) print(mdist) #---------------------------------------------------------------------------------------------------------------------------- import numpy as np def manhattan_distance(a, b): return np.sum(np.abs(a - b)) a = np.array([1, 1, 5, 6, 7, 8]) b = np.array([2, 2, 3, 6, 4, 6]) dist = manhattan_distance(a, b) print(dist) from scipy.spatial.distance import cityblock dist = cityblock(a, b) print(dist) #---------------------------------------------------------------------------------------------------------------------------- Öklit ve Manhattan uzaklıklarının daha genel bir biçimine Minkowski uzaklığı da denilmektedir. x ve y noktaları arasındaki Minkowski uzaklığı şöyle ifade edilebilir: Minkowski Uzaklığı = (sigma(abs(xi - yi)^p))^ (1/p) Burada p = 2 ise Öklit uzaklığı p = 0 ise Manhattan uzaklığı söz konusu olmaktadır. #---------------------------------------------------------------------------------------------------------------------------- #---------------------------------------------------------------------------------------------------------------------------- Ö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 tercih edilen bir uzaklık türüdür. Hamming uzaklığı "farklı olan elemanların sayısının toplam eleman sayısına oranı" ile hesaplanmaktadır. Örneğin: ankara ayazma Bu iki yazının hamming uzaklığı 4/6'dır. Hamming uzaklığı SciPy kütüphanesinde scipy.spatial.distance modülündeki hamming isimli fonksiyonla hesaplanabilir. Ö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 #---------------------------------------------------------------------------------------------------------------------------- #---------------------------------------------------------------------------------------------------------------------------- 84. Ders - 07/12/2024 - Cumartesi #---------------------------------------------------------------------------------------------------------------------------- #---------------------------------------------------------------------------------------------------------------------------- Kosinüs uzaklığı da bazı uygulamalarda kullanılmaktadır. İki nokta arasındaki açının kosinüsü ile hesaplanmaktadır. Bu uzaklık da scipy.spatial.distance modülündeki cosine fonksiyonu ile hesaplanmaktadır. Örneğin: import numpy as np from scipy.spatial.distance import cosine a = np.array([1, 0, 0, 1]) b = np.array([1, 1, 0, 0]) hdist = cosine(a, b) print(hdist) # 0.5 #---------------------------------------------------------------------------------------------------------------------------- import numpy as np from scipy.spatial.distance import cosine a = np.array([1, 0, 0, 1]) b = np.array([1, 1, 0, 0]) hdist = cosine(a, b) print(hdist) # 0.5 #---------------------------------------------------------------------------------------------------------------------------- Bazı algoritmalarda (örneğin agglomerative hiyerarşik kümeleme algoritmasında) belli sayıda noktanın biribiri arasındaki tüm uzaklıklarının bir uzaklık matirisi biçiminde hesaplanması gerekebilmektedir. Bunun için SciPy kütüphanesinde scipy.spatial.distance modülünde pdist ve cdist fonksiyonları bulundurulmuştur. cdist fonksiyonu bize simetrik bir matris verirken pdist fonksiyonu tek boyutlu kompakt hale getirilmiş bir diziyi (yani simetrik matrisin yalnızca bir yarısının tek boyutlu dizi haline getirilmiş biçimini) vermektedir. Bu fonksiyonların ayrıntılarını dokümanlardan inceleyebilirsiniz. Aşağıda bir örnek kullanım verilmiştir. #---------------------------------------------------------------------------------------------------------------------------- import numpy as np from scipy.spatial.distance import pdist, cdist points = np.array([[1, 2], [1, 3], [3, 1], [4, 2], [2, 3]]) result = cdist(points, points, metric='euclidean') print(result) print('-' *20) result = pdist(points, metric='euclidean') print(result) #---------------------------------------------------------------------------------------------------------------------------- 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. Fakat bu modülde bulunmayana ancak kümeleme işlemlerinde kullanılan başka uzaklıklar da bulunmaktadır. #---------------------------------------------------------------------------------------------------------------------------- #---------------------------------------------------------------------------------------------------------------------------- Pek çok uzaklıklık türü sütunsal biçimde hesaplandığına için sütunlar arasındaki skala farklılıkları bu uzaklık hesaplarını olumsuz etkileyecektir. Ö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 Öklit uzaklığında asıl etkili olan sütun üçüncü sütundur. Birinci sütunun neredeyse hiçbir etkisi yoktur. O halde bizim bu tür uzaklık hesaplarında sütunların skalalarını benzer hale getirmemiz gerekir. Yani kümeleme işlemlerinde çoğu kez özellik ölçeklemesinin uygulanması gerekebilmektedir. Tabii bazı uzaklık ölçütleri (örneğin kosinüs uzaklığı gibi) sütunların skala farklılıklarından olumsuz etkilenmez. Ancak Öklük uzaklığı gibi, Manhattan uzaklığı gibi uzaklıklar bu skala farklılıklarından etkilenmektedir. #---------------------------------------------------------------------------------------------------------------------------- #---------------------------------------------------------------------------------------------------------------------------- Veri kümesinde kategorik veriler varsa uzaklık yöntemlerinin bir bölümü bu kategorik verilerde anlamlı olmaktan çıkabilecektir. Örneğin Öklit uzaklığının kullanıldığını düşünelim. Aşağıdaki gibi bir veri kümesi söz konusu olsun: F1 F2 F3 0.2 123 Ankara 0.4 567 İzmir 0.7 328 Eskişehir ..... Burada F3 özelliğinin şehirlere ilişkin kategorik olduğunu görüyorsunuz. Türkiye'de şu anda 81 şehir vardır. Biz eğer bu sütunu LabelEncoder iile sayısal hale getirirsek bu kategorileri [0, 81] arasında bir sayıya dönüştürürüz. Bu durumda 0 ile 80 arasındaki uzaklık 0 ile 1 arasındaki uzaklıktan çok fazla hale gelecektir. Örneğin böyle bir dönüştürmede Ankara ile Zonguldak arasındaki uzaklık Ankara ile Ağrı arasındaki uzaklıktan çok fazla hale gelecektir. Halbuki bunun bizim için mantıksal hiçbir gerekçesi yoktur. Bu tür kategorik sütunları one-hot-encoding yaptığımızda da bu kategoriler arasında bir farklılık oluşmayacaktır. İşte buradan da görüldüğü gibi aslında kategorik sütunlar için başka uzaklık ölütlerinin kullanılması uygun olmaktadır. Örneğin Hamming uzaklığı bu amaçla kullanılabilmektedir. Pekiyi bir veri kümesi hem nümerik hem de kategorik sütunlar içeriyorsa bu durumda nasıl bir uzaklık yöntemi uygulanmalıdır? İşte bu tür durumlarda seçeneklerden biri hem nümerik hem de kategorik sütunlarla çalışabilecek başka bir uzaklık yöntemi seçmektir. Diğeri ise kümeleme algoritmasını bu duruma uygun olarak değiştirmektir. Kategorik verilerin de bulunduğu veri kümelerinde kategorik sütunları farklı bir biçimde ele alan "Gower Uzaklığı" denilen bir uzaklık da kulanılmaktadır. #---------------------------------------------------------------------------------------------------------------------------- #---------------------------------------------------------------------------------------------------------------------------- Bu bölümde kümeleme işlemlerinde kullanılan kümeleme algoritmaları ve bu algoritmaları uygulayan fonksiyonlar ve sınıflar üzerinde duracağız. #---------------------------------------------------------------------------------------------------------------------------- #---------------------------------------------------------------------------------------------------------------------------- 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ığı fikir 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 ve yaygın olanı ve en iyi bilineni K-Means denilen algoritmadır. K-Means ağırlık merkezi temelli bir algoritmadır. Buradaki "Means" ağırlık merkezi oluştururken ortalamanın dikkate alınması nedeniyle kullanılmış olan biz sözcüktür. Aslında ağırlık merkezi oluşturulurken ortalamanın dışında başka hesaplamalar da kullanılabilmektedir. Dolayısıyla bu yöntemin K-XXX biçiminde (burada XXX alt yöntemi belirten bir isimdir) varyasyonları vardır. Bu varyasyonların bazıları şunlardır: K-Medoids K-Modes K-Prototypes K-Centers K-Medians K-Nearest Neighbors Ancak bu aileden en çok kullanılanı ve K-Means isimli algoritmadır. #---------------------------------------------------------------------------------------------------------------------------- #---------------------------------------------------------------------------------------------------------------------------- K-Means kümeleme algoritmasında işin başında uygulamacının noktalardan kaç küme oluşturulacağını belirlemiş olması gerekir. Küme sayıları bazı uygulamalarda zaten biliniyor durumda olabilir. Örneğin çok sayıda resim söz konusu olabilir ve bu resimlerin 10 farklı meyveye ilişkin olduğu zaten biliniyor olabilir. Ancak bazı uygulamalarda küme sayısını uygulamacı da bilmiyor olabilir. Bu tür durumlarda uygun küme sayısının belirlenmesi ayrı bir problem biçiminde karşımıza çıkmaktadır. Biz burada küme sayısının baştan bilindiğini ve bunun k olduğunu varsayacağız. (K-Means ismindeki K harfi de k tane küme sayısından gelmektedir.) Algoritmanın tipik işleyişi şöyledir: 1) k tane küme için işin başında rastgele k tane ağırlık merkezi belirten nokta oluşturulur. Bu noktalar mevcut noktalar içerisinden rastgele seçilebilir. 2) Tüm noktaların bu k tane 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 k tane kümeden oluşan ilk kümeleme yapılmıştır. 3) 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 Zaten bu yönteme K-Means ismi ağırlık merkezi bulunurken her boyutun kendi aralarındaki ortalamasının hesaplanması nedeniyle verilmiştir. 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. 4) 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 3'üncü adıma geri dönülür ve işlemler bu biçimde devam ettirlir. 5) Eğer yeni ağırlık merkezine göre hiçbir nokta küme değiştirmiyorsa artık yapılacak bir şey kalmamıştır ve algoritma sonlandırılır. K-Means yönteminin burada uygulanan algoritmasına "Lloyd" algoritması denilmektedir. Bu algoritma Stuart Lloyd tarafından 1957 yılında geliştirilmiştir. #---------------------------------------------------------------------------------------------------------------------------- #---------------------------------------------------------------------------------------------------------------------------- Şimdi K-Means Llyod algoritmasına sayısal bir örnek verelim. İki özelliği olan aşağıdaki gibi bir veri kümesi söz konusu olsun: Sıra X1 X2 1 7 8 2 2 4 3 6 4 4 3 2 5 6 5 6 5 7 7 3 3 8 1 4 9 5 4 10 7 7 11 7 6 12 2 1 Biz bu noktaları iki kümeye ayırmak isteyelim. Yani k değerinin 2 olduğunu varsayalım. Algoritmaya iki ağırlık merkezi oluşturmakla başlayalım. Bu ağırlık merkezlerinin şunlar olduğunu varsayalım: C1 = (1, 4) C2 = (7, 8) Şimdi buradaki 12 noktanın tek tek bu iki ağırlık merkezine Öklit uzaklıklarını hesaplayıp ilk kümeleri oluşturalım: Sıra X1 X2 C1 Uzaklığı C2 Uzaklığı Atanan Küme ------------------------------------------------------------------- 1 7 8 7.21 0 Cluster-2 2 2 4 1.00 6.40 Cluster-1 3 6 4 5.00 4.12 Cluster-2 4 3 2 2.83 7.21 Cluster-1 5 6 5 5.10 3.16 Cluster-2 6 5 7 5.00 2.24 Cluster-2 7 3 3 2.24 6.40 Cluster-1 8 1 4 0.00 7.21 Cluster-1 9 5 4 4.00 4.47 Cluster-1 10 7 7 6.71 1.00 Cluster-2 11 7 6 6.32 2.00 Cluster-2 12 2 1 3.16 8.60 Cluster-1 Şimdi iki kümenin gerçek ağırlık merkezlerini hesaplayalım. Bu gerçek ağırlık merkezleri şöyledir: C1 = (2.67, 3.00) C2 = (6.33, 6.17) Şimndi yeniden 12 noktanın da bu yeni ağırlık merkezlerine uzaklıklarını hesaplayıp yeni kümeleri elde edelim: Sıra X1 X2 C1 Uzaklığı C2 Uzaklığı Atanan Küme ----------------------------------------------------------------------- 1 7 8 6.61 1.95 Cluster-2 2 2 4 1.20 4.84 Cluster-1 3 6 4 3.48 2.19 Cluster-2 4 3 2 1.05 5.34 Cluster-1 5 6 5 3.88 1.22 Cluster-2 6 5 7 4.63 1.57 Cluster-2 7 3 3 0.33 4.60 Cluster-1 8 1 4 1.95 5.75 Cluster-1 9 5 4 2.54 2.55 Cluster-1 10 7 7 5.89 1.07 Cluster-2 11 7 6 5.27 0.69 Cluster-2 12 2 1 2.11 6.74 Cluster-1 Bu işlemden sonra kümelerde bir değişiklik olmadığı için algoritma sonlandırılacaktır. #---------------------------------------------------------------------------------------------------------------------------- #---------------------------------------------------------------------------------------------------------------------------- K-Means yönteminde işin başında ağırlık merkezleri rastgele alındığı için algoritmanın her çalıştırılmasında birbirinden farklı kümeler elde edilebilmektedir. (Tabii bu kümelerin çok az sayıda elemanı farklı olabilmektedir.) Bu tür durumlarda algoritma birden fazla kez çalıştırılıp en iyi kümeleme seçilebilir. Pekiyi K-Means yönteminde performas ölçütü olarak neyi kullanabiliriz? Yani iki alternatif kümelemede hangi kümelemenin daha iyi olduğunu nasıl ölçebiliriz? İşte 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. Yani aslında bu yöntemde en küçük toplam varyansa bakılmaktadır. O halde biz K-Means algoritmasını birden fazla kez çalıştırıp ehr kümelemenin ataletine bakıp en iyi atalete sahip olan kümelemeyi seçebiliriz. 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 K-Means algoritmasında kestirimde bulunabilir miyiz? Yani kümeleme işleminden sonra elimizdeki bir noktanın hangi kğmeye ilişkin olabileceğini kestirebilir miyiz? Eğer bir kez kümeleme yapılmışsa yeni bir noktanın bu kümelerden hangisinin içerisine girebileceği basit bir biçimde noktanın tüm ağırlık merkezlerine uzaklığına bakı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 apısı şöyledir: kmeans(dataset, nclusters, centroids=None) Fonksiyonun birinci parametresi kümelenecek olan noktaları kinci parametresi ise oluşturulacak küme sayını belirtir. Üçüncü parametre başlangıçtaki 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 tek 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ını belirtir. Buna atalet (inertia) dendiğini anımsayınız. Burada gerçekleştirdiğimiz algoritmada ağırlık merkezlerinin rastgele seçilmesinde bir sorun vardır. Eğer işin başında rastgele seçilen ağırlık merkezine yakın hiçbir nokta yoksa (yani işin başında kümelerden biri boş olursa) algoritmada bir daha o kümenin içerisine herhangi bir nokta dahil edilememektedir. Bu nedenle işin başındaki rastgele ağırlık merkezlerinin seçimine dikkat edilmelidir. "K-means++" denilen ilkdeğer verme algoritmasını inleyerek o algoritmayı uygulayabilirsiniz. K-Means kümeleme yöntemi temel olarak Öklit uzaklığına dayalı bir ağırlık merkezi oluşturmaktadır. Kategorik verilerin Öklit uzaklıklarının anlamlı olmadığını belirtmiştik. Bu nedenle K-Means algoritması özünde nümerik sütunlara sahip veri kümelerinde kullanılmaktadır. Kategorik sütunlara da sahip olan veri kümeleri için dolaylı önişlemler yapılarak K-Means yöntemi kullanılabilir. Ancak bu tür veri kümeleri için K-Prototypes gibi diğer alternatifler daha uygun bir seçenek oluşturmaktadır. Aşağıdaki örnekte kullanılan "points.csv" dosyasının içeriği şöyledir: X1,X2 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): random_indices = np.random.choice(dataset.shape[0], size=nclusters, replace=False) centroids = dataset[random_indices] return centroids import pandas as pd df = pd.read_csv('points.csv', dtype='float32') dataset = df.to_numpy(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], color='ygb'[i]) 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() #---------------------------------------------------------------------------------------------------------------------------- 85. Ders - 08/12/2024 - Pazar #---------------------------------------------------------------------------------------------------------------------------- #---------------------------------------------------------------------------------------------------------------------------- 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: class sklearn.cluster.KMeans(n_clusters=8, *, init='k-means++', n_init='auto', max_iter=300, tol=0.0001, verbose=0, random_state=None, copy_x=True, algorithm='lloyd') Metodun n_clusters parametresi ayrıştırılacak küme sayısını (k değerini), init parametresi başlangıçtaki rastgele ağırlık merkezlerinin nasıl oluşturulacağını belirtmektedir. init parametresinin default değeri "kmeans++" biçimindedir. Bu parametreye "random" değeri de girilebilir. Bu durumda ilk ağırlık merkezleri rastgele satırlardan seçilecektir. Ayrıca bu parametreye programcı kendi ağırlık merkezlerini bir NumPy matrisi biçiminde de girebilir. Metodun n_init parametresi algoritmanın kaç kere çalıştırılıp en iyisinin bulunacağını belirtmektedir. Bu parametrenin default değeri "auto" biçimdedir. Bu "auto" default değeri kullanıldığında algoritmanın keç kez çalıştırılacağı metodun init parametresine bağlı olarak değişmektedir. Eğer init parametresi "k-means++" ya da NumPy dizisi biçimindeyse algoritma 1 kez çalıştırılır, "rasndom" biçimindeyse 10 kez çalıştırılır. En iyi değer "atalete (inertia)" bağlı olarak belirlenmektedir. Metodun 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 algorithm 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 Llyod algoritmasıdır. Ancak noktaların durumuna göre bu varyasyonlar arasında hız açısından farklılıklar söz konusu olabilmektedir. KMeans nesnesi yaratıldıktan sonra kümeleme algoritması fit metodu ile çalıştırılır. fit metodu parametre olarak veri kümesini iki boyutlu bir matris biçiminde bizden alır ve kümelemeyi yapar, nesnenin kendisiyle geri döner. Kümeleme işlemi bittikten sonra nesnenin aşağıda belirttiğimiz özniteliklerinden kümeleme sonucundaki 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 tek boyutlu bir NumPy dizisini belirtir. Buradaki kümeler 0'dan başlanarak numaralandırılmıştır. Örneğin biz labels_ özniteliğinden aşağıdaki gibi bir NumPy dizisi elde edebiliriz: array([2, 1, 0, 1, 0, 2, 1, 1, 0, 2, 2, 1]) Burada sırasıyla noktaların kaç numaralı kümeye ilişkin olduğu belirtilmektedir. Kümelemede kümelenmiş olan olguların ne olduğu bilinmemektedir dolayısıyla da bunlara bir isim verilememektedir. KMeans sınıfı bize ayrıca kümelerdeki noktaları vermemektedir. Ancak biz bu öznitelikten hareketle hangi noktaların hangi kümelerin içerisinde olduğunu dataset[km.labels_ == n] işlemi ile elde edebiliriz. inertia_: Bu öznitelik tüm noktaların kendi ağırlık merkezlerine uzaklıklarının karelerinin toplamını vermektedir. Bu değerin bir performans ölçütü olarak kullanıldığını belirtmiştik. 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ını belirtir. Sınıfın transform metodu önemli bir işlem yapmamaktadır. transform metoduna biz birtakım noktalar verdiğimizde metot bize o noktaların tüm ağırlık merkezlerine uzaklığını verir. Benzer biçimde fit_transform metodu da önce fit işlemi ile önce kümelemeyi yapıp 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ı işleve sahiptir. 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ı iki boyutlu bir NumPy dizisi biçiminde 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ı hesaplayıp en yakın ağırlık mrkezinin ilişkin olduğu kümeyi vermektedir. Sınıfın fit_predict isimli metodu ise ö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ğıda KMeans sınıfının kullanımına ilişkin bir örnek verilmiştir. Örnekte kümeleme işlemi şöyle yapılmıştır: df = pd.read_csv('points.csv') dataset = df.to_numpy(dtype='float32') km = KMeans(n_clusters=NCLUSTERS, n_init=10) km.fit(dataset) Biz burada küme sayısını temsil eden NCLUSTERS değerini 3 olarak belirledik. Örnekte kümeleme sonucunda elde edilen tüm bilgiler yazdırılmıştır ve her kümedeki noktalar saçılma grafiği ile gösterilmiştir. Ayrıca örnekte bir kestirim işlemi yapılıp kestirilen noktaların da hangi kümeler içerisine düştüğü ayrı bir grafikle gösterilmiştir. Örnekte kullandığımız "points.csv" dosyasının içeriği de şöyledir: X1,X2 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 pandas as pd from sklearn.cluster import KMeans df = pd.read_csv('points.csv') dataset = df.to_numpy(dtype='float32') km = KMeans(n_clusters=NCLUSTERS, n_init=10) km.fit(dataset) print(f'Inertia: {km.inertia_}') print('-' * 20) for i in range(NCLUSTERS): print(f'Cluster {i}', end='\n\n') cluster = dataset[km.labels_ == i] print(cluster) print('-' * 20) print('Dataset', end='\n\n') df['Cluster'] = km.labels_ print(df) print('-' * 20) print('Centroids') print(km.cluster_centers_) import matplotlib.pyplot as plt plt.title('Clustered Points', fontsize=12) 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') plt.show() import numpy as np predict_dataset = np.array([[3, 4], [4, 6], [6, 2]], dtype='float32') predict_result = km.predict(predict_dataset) print(f'Predict result: {predict_result}') cluster_colors = np.array(['orange', 'green', 'magenta']) plt.title('Clustered Points With Predicted Data', fontsize=12) for i in range(NCLUSTERS): plt.scatter(dataset[km.labels_ == i, 0], dataset[km.labels_ == i, 1], color=cluster_colors[i]) plt.scatter(km.cluster_centers_[:, 0], km.cluster_centers_[:, 1], 60, color='red', marker='s') plt.scatter(predict_dataset[:, 0], predict_dataset[:, 1], 60, color=cluster_colors[predict_result], marker='^') plt.show() #---------------------------------------------------------------------------------------------------------------------------- Şimdi de K-Means kümeleme yöntemini daha önce üzerinde çalıştığımız "zambak (iris)" veri kümesine uygulayalım. Anımsanacağı gibi "zambak" 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ırmaya çalışalım. Önce veri kümesini okuyup özellik ölçeklemesi yapalım: NCLUSTERS = 3 df = pd.read_csv('iris.csv') dataset = df[['SepalLengthCm','SepalWidthCm','PetalLengthCm','PetalWidthCm']].to_numpy('float32') ss = StandardScaler() ss.fit(dataset) transformed_dataset = ss.transform(dataset) Sonra KMeans algoritmasını ölçeklendirilmiş verilere uygulayalım: from sklearn.cluster import KMeans km = KMeans(n_clusters=NCLUSTERS, n_init=10) km.fit(transformed_dataset) df['Cluster'] = km.labels_ Şimdi kümelenmiş noktaların grafiğini çizdirelim. Ancak noktalar dört boyutlu uzaya ilişkin olduğu için onu iki boyuta indirgeyeceğiz. Bu işleme "boyutsal özellik indirgemesi (dimentionality feature reduction)" denilmektedir. Bu konu ilerde ayrı bir bölümde ele alınacaktır. Biz bu işlemi ismine "temel bileşenler analizi (principle component analysis)" denilen yöntemle aşağıdaki gibi gerçekleştireceğiz: from sklearn.decomposition import PCA pca = PCA(n_components=2) pca.fit(dataset) reduced_dataset = pca.transform(dataset) Artık iki boyuta indirgenmiş olan noktaların saçılma grafiğini çizebiliriz. Burada her kümenin noktalarını ayrı bir renkle görüntüleyeceğiz: transformed_centroids = ss.inverse_transform(km.cluster_centers_) reduced_centroids = pca.transform(transformed_centroids) plt.title('Clustered Points', fontsize=12) for i in range(NCLUSTERS): plt.scatter(reduced_dataset[km.labels_ == i, 0], reduced_dataset[km.labels_ == i, 1]) plt.scatter(reduced_centroids[:, 0], reduced_centroids[:, 1], color='red', marker='s') plt.show() Kestirim işlemleri benzer biçimde yapılabilir: 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='float32') transformed_predict_data = ss.transform(predict_data) predict_result = km.predict(transformed_predict_data) print(predict_result) Örneğin kodları bir bütün olarak aşağıda verilmiştir. #---------------------------------------------------------------------------------------------------------------------------- NNCLUSTERS = 3 import pandas as pd df = pd.read_csv('iris.csv') dataset = df[['SepalLengthCm','SepalWidthCm','PetalLengthCm','PetalWidthCm']].to_numpy('float32') from sklearn.preprocessing import StandardScaler ss = StandardScaler() ss.fit(dataset) transformed_dataset = ss.transform(dataset) from sklearn.cluster import KMeans km = KMeans(n_clusters=NCLUSTERS, n_init=10) km.fit(transformed_dataset) df['Cluster'] = km.labels_ import matplotlib.pyplot as plt from sklearn.decomposition import PCA pca = PCA(n_components=2) pca.fit(dataset) reduced_dataset = pca.transform(dataset) transformed_centroids = ss.inverse_transform(km.cluster_centers_) reduced_centroids = pca.transform(transformed_centroids) plt.title('Clustered Points', fontsize=12) for i in range(NCLUSTERS): plt.scatter(reduced_dataset[km.labels_ == i, 0], reduced_dataset[km.labels_ == i, 1]) plt.scatter(reduced_centroids[:, 0], reduced_centroids[:, 1], color='red', marker='s') 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='float32') transformed_predict_data = ss.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ı belirlemiş olmamız gerekir. Pekiyi biz bunu nasıl belirleyebiliriz? İşte bazen problemin kendi içerisinde zaten küme sayısı bilinmektedir. Örneğin birisi bize çok sayıda elma, armut, kayısı, şeftali, karpuz resimleri vermiş olsun. Ama biz hangi resmin hangi meyveye ilişkin olduğunu bilmiyor olalım. Bu problemde biz resimlerin beş farklı meyveye ilişkin olduğunu zaten bilmekteyiz. Ancak hangi meyvelerin hangi resimlerle ilişkili oluğunu bilmemekteyiz. Tabii pek çok durumda biz küme sayısını da bilmiyor durumda oluruz. En iyi küme sayısının belirlenmesi için birkaç yöntem kullanılmaktadır. En çok kullanılan iki yöntem şöyledir: 1) Dirsek Noktası Yöntemi (Elbow Point Method) 2) Silüet Yöntemi (Silhouette Method) #---------------------------------------------------------------------------------------------------------------------------- #---------------------------------------------------------------------------------------------------------------------------- Dirsek noktası 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 grafikte "eğrinin yataya geçtiği nokta" gözle tespit edilir. Eğrinin yataya geçtiği noktaya "dirsek noktası (elbow point)" denilmektedir. Ancak dirsek noktası yöntemi gözle tespite dayılıdır. Bu yöntemde uygulamacı tarafından dirsek gözle tespit edilmesi sırasında tereddütler oluşabilmektedir. Aşağıdaki örnekte daha önce kullanmış olduğumuz "points.csv" noktaları için dirsek noktası grafiği çizilmiştir. Bu örnekte toplam ataletler aşağıdaki gibi bir liste içlemi ile elde edilmiştir: inertias = [KMeans(n_clusters=i, n_init=10).fit(dataset).inertia_ for i in range(1, 10)] Grafik şöyle çizdirilmiştir: plt.title('Elbow Point Method', fontsize=12) plt.plot(range(1, 10), inertias) plt.show() Buradan elde edilen grafiğe bakıldığında dirsek noktasının 3 ya da 4 olabileceği anlaşılmaktadır. Örnekte kullanılan noktalar şöyledir: X1,X2 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 pandas as pd from sklearn.cluster import KMeans df = pd.read_csv('points.csv') dataset = df.to_numpy(dtype='float32') inertias = [KMeans(n_clusters=i, n_init=10).fit(dataset).inertia_ for i in range(1, 10)] import matplotlib.pyplot as plt plt.title('Elbow Point Method', fontsize=12) plt.plot(range(1, 10), inertias) plt.show() # Dirsek noktasının 3 olduğu tespit edilmiştir km = KMeans(n_clusters=3, n_init=10) km.fit(dataset) plt.title('Clustered Points', fontsize=12) for i in range(3): 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') #---------------------------------------------------------------------------------------------------------------------------- Şimdi de zambak (iris) veri kümesi için dirsek noktası yöntemiyle en iyi küme sayısını belirlemeye çalışalım. Bu işlem sonucunda elde edilen grafikten dirsek noktasının 3 olduğu görülmektedir. #---------------------------------------------------------------------------------------------------------------------------- import pandas as pd df = pd.read_csv('iris.csv') dataset = df[['SepalLengthCm','SepalWidthCm','PetalLengthCm','PetalWidthCm']].to_numpy('float32') from sklearn.preprocessing import StandardScaler ss = StandardScaler() ss.fit(dataset) transformed_dataset = ss.transform(dataset) from sklearn.cluster import KMeans inertias = [KMeans(n_clusters=i, n_init=10).fit(transformed_dataset).inertia_ for i in range(1, 10)] import matplotlib.pyplot as plt plt.title('Elbow Point Method', fontsize=12) plt.plot(range(1, 10), inertias) plt.show() #---------------------------------------------------------------------------------------------------------------------------- 86. Ders - 14/12/2024 - Cumartesi #---------------------------------------------------------------------------------------------------------------------------- #---------------------------------------------------------------------------------------------------------------------------- Silüet (silhouette) yönteminde yine 2'den başlanarak belli sayıda küme için çözümler yapılır. Sonra her çözüm için "silüet skoru (silhouette score)" denilen bir değer elde edilmektedir. Bu değerin en yüksek olduğu küme sayısından bir fazla küme sayısı en iyi küme sayısı olarak belirlenmktedir. Silüet skoru 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) verilir. Silüet skor işlemi tek kümeyle yapılamamaktadır. Yani bu yöntemde silüet skorları kümesi 2'den başlatılarak hesaplanmalıdır. silhouette_score fonksiyonun parametrik yapısı şöyledir: sklearn.metrics.silhouette_score(X, labels, *, metric='euclidean', sample_size=None, random_state=None, **kwds) Fonksiyonun birinci parametresi kümelenecek veri kümesini ikinci parametresi ise kümeleme sonucunda elde edilmiş olan kümeleme bilgisini (yani KMeans nesnesinin labels_ özniteliğini) almaktadır. Şimdi yukarıdaki "points.csv" veri kümesi için en iyi küme sayısını silüet skoru ile tespit edelim. Aşağıdaki gibi bir döngü ile küme sayıları için silüet skor değerleri elde edilebilir: ss_list = [] for i in range(2, 10): labels = KMeans(n_clusters=i, n_init=10).fit(dataset).labels_ ss = silhouette_score(dataset, labels) ss_list.append(ss) print(f'{i} => {ss}') Buradan elde edilen skorlar şöyledir: 2 => 0.5544097423553467 3 => 0.47607627511024475 4 => 0.4601795971393585 5 => 0.4254012405872345 6 => 0.3836685121059418 7 => 0.29372671246528625 8 => 0.21625620126724243 9 => 0.11089805513620377 Bizim amacımız en yüksek skora ilişkin küme sayısından bir bir fazlasını elde etmektir. Gözle baktığımızda en yüksek değerin 0.5544097423553467 olduğu görülmektedir. Bu değer 2 kğmeye ilişkin olan değerdir. O halde en iyi küme sayısı 3'tür. Bu işlemi şöyle de yapabiliriz: optimal_cluster = np.argmax(ss_list) + 3 Tabii aslında fonksiyonel tarzda bu tespit işlemi tek bir ifade ile de yapılabilirdi: optimal_cluster = np.argmax([silhouette_score(dataset, KMeans(i, n_init=10).fit(dataset).labels_) for i in range(2, 10)]) + 3 Aşağıdaki örnekte dana önceden kullandığımız "points.csv" verileir için Silhouette skor değeri elde edilmiştir. #---------------------------------------------------------------------------------------------------------------------------- import pandas as pd from sklearn.cluster import KMeans df = pd.read_csv('points.csv') dataset = df.to_numpy(dtype='float32') import numpy as np from sklearn.metrics import silhouette_score ss_list = [] for i in range(2, 10): labels = KMeans(n_clusters=i, n_init=10).fit(dataset).labels_ ss = silhouette_score(dataset, labels) ss_list.append(ss) print(f'{i} => {ss}') optimal_cluster = np.argmax(ss_list) + 3 print(f'Optimal cluster: {optimal_cluster}' ) optimal_cluster = np.argmax([silhouette_score(dataset, KMeans(i, n_init=10).fit(dataset).labels_) for i in range(2, 10)]) + 3 print(f'Optimal cluster: {optimal_cluster}' ) #---------------------------------------------------------------------------------------------------------------------------- Aşağıdaki örnekte yine zambak (iris) veri kümesi için en uygun küme sayısı silüet skor yöntemi ile elde edilmiştir. Elde edilen skor deeğerleri şöyledir: 2 => 0.6808136105537415 3 => 0.5525919795036316 4 => 0.4978257119655609 5 => 0.48851755261421204 6 => 0.3712180554866791 7 => 0.35612809658050537 8 => 0.3631969094276428 9 => 0.34673967957496643 Burada en yüksek değerin 2'li kümelemeye ilişkin olduğunu görüyorsunuz. Bu durumda en uygun küme sayısı 3 olacaktır. #---------------------------------------------------------------------------------------------------------------------------- import pandas as pd df = pd.read_csv('iris.csv') dataset = df[['SepalLengthCm','SepalWidthCm','PetalLengthCm','PetalWidthCm']].to_numpy('float32') from sklearn.preprocessing import StandardScaler ss = StandardScaler() ss.fit(dataset) transformed_dataset = ss.transform(dataset) from sklearn.cluster import KMeans import numpy as np from sklearn.metrics import silhouette_score ss_list = [] for i in range(2, 10): labels = KMeans(n_clusters=i, n_init=10).fit(dataset).labels_ ss = silhouette_score(dataset, labels) ss_list.append(ss) print(f'{i} => {ss}') optimal_cluster = np.argmax(ss_list) + 3 print(f'Optimal cluster: {optimal_cluster}' ) #---------------------------------------------------------------------------------------------------------------------------- Silüet Skor Yönteminin Açıklanması Eklenecek #---------------------------------------------------------------------------------------------------------------------------- #---------------------------------------------------------------------------------------------------------------------------- Biz yukarıdaki örneklerde bütün sütunları nümerik olan veri kümeleri üzerinde K-Means yöntemini uyguladık. Pekiyi kategorik sütunlar da içeren veri kümelerinde K-Means kümeleme işlemi nasıl uygulanacaktır? Anımsanacağı gibi K-Means kümeleme yönteminde Öklit uzaklığı kullanılmaktadır. Öklit uzaklığı nümerik değerlerle hesaplanan bir uzaklık cinsidir. O halde biz kategorik sütunlar da içeren veri kümeleri için K-Means yöntemini kullanacaksak bir biçimde bu kategorik sütunları sayısal hale dönüştürmemiz gerekir. Pekiyi bunu nasıl yapabiliriz? Örneğin aşağıdaki gibi bir veri kümesi olsun: A B C Renk ---------------------------- 3.2 2.1 4.2 Kırmızı 2.1 4.2 3.2 Mavi 6.1 5.2 1.2 Yeşil 4.1 1.2 3.2 Yeşil Burada "Renk" sütunu kategorik bir sütundur. Uzaklığı hesaplanacak iki nokta şöyle olsun: 2.1 4.2 3.2 Mavi 4.1 1.2 3.2 Yeşil Buradaki kategorik değerler nasıl sayısallaştırılacaktır? Bunun için biz LabelEncoder kullanamayız. Çünkü LabelEncoder her kategoriye 0'dan itibaren bir tamsayı karşılık düşürmektedir. Bu durumda gerçekte var olmayan renkler arasında anlamsız bir uzaklık oluşur. (Bu tür durumlarda Hamiing uzaklığının tercih edildiğini anımsayınız.) Bu tür durumlarda one-hot-encoding dönüştürmesi nispeten iş görebilmektedir. Bu dönüştürme yapıldığında aynı renkler arasındaki uzaklık 0 farklı renkler arasındaki uzaklık sabit fakat aynı bir değer haline gelir. Tabii one-hot-encoding dönüştürmesinden sonra özellik ölçeklemesinin yapılması gerekecektir. Her ne kadar one-hot-encoding dönüştürmesi bu tür durumlarda kullanılabiliyorsa da bu uygulamanın bazı dezavantakları da vardır. Kategorik sütunlarla sayısal sütunları bir araya getirmek bazı bilgi kayıplarına yol açabilmektedir. Ayrıca sütun sayısının fazlalaşması diğer bir problem olarak karşımıza çıkacaktır. One-hot-encoding yapılmış fazla sayıda sütun nümerik işlemleri yavaşlatır ve diğer sütunların etkisini azaltabilir. Hem kategorik hem de nümerik sütunların bulunduğu veri kümelerinde K-Means uygulamadan önce kategorik sütunlar tamamen veri kümesinden de atılabilir. Ancak şüphesiz bu işlem bilgi kaybına yol açacaktır. #---------------------------------------------------------------------------------------------------------------------------- #---------------------------------------------------------------------------------------------------------------------------- Şimdi bir sektördeki müşterilere ilişkin kategorik sütunlar da içeren ("mixed") "customer clustering" isimli veri kümesi üzerinde K-Means uygulamaya çalışalım. 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'. "Sex" sütunu cinsiyet belirten iki kategorili bir sütundur. "Marital status" yine iki kategorili, "Education" 4 kategorili, "Occupation" 3 kategorili ve "Settlement size" da 3 kategorili sütunlardır. Veri kümesinin görünümü aşağıdaki gibidir: ID,Sex,Marital status,Age,Education,Income,Occupation,Settlement size 100000001,0,0,67,2,124670,1,2 100000002,1,1,22,1,150773,1,2 100000003,0,0,49,1,89210,0,0 100000004,0,0,45,1,171565,1,1 100000005,0,0,53,1,149031,1,1 ... Veri kümesinde fazla sayıda kategorik sütun olduğunu görüyorsunuz. Aslında bu veri kümesi için K-Means uygun bir kümeleme yöntemi değildir. Ancak biz burada 2'den fazla olan kategorik sütunları one-hot-encoding yapıp K-Means yöntemini uygulayacağız. Örneğin kodlarını aşağıda veriyoruz. #---------------------------------------------------------------------------------------------------------------------------- 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'], dtype='uint8') from sklearn.preprocessing import StandardScaler ss = StandardScaler() scaled_dataset = ss.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() import numpy as np from sklearn.metrics import silhouette_score optimal_cluster = np.argmax([silhouette_score(scaled_dataset, KMeans(i, n_init=10).fit(scaled_dataset).labels_) for i in range(2, 10)]) + 3 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=(12, 10)) 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() #---------------------------------------------------------------------------------------------------------------------------- Kümeleme işlemlerinde kümelenecek nokta sayısı çok fazla ise ve bu bakımdan bir bellek sorunu ortaya çıkıyorsa tine parçalı eğitim uygulamak gerekebilir. Ancak her kğümeleme yöntemi parçalı eğitime uygun değildir. Başka bir deyişle her kümeleme yöntemi parçalı eğitime uygun değildir. K-Means kümeleme yöntemi parçalı eğitim yapmaya nispetem uygun bir yöntemdir. scikit-learn kütüphanesinde K-Means yöntemi ile parçalı eğitim yapmak için MiniBatchKMeans isimli bir sınıf da bulundurulmuştur. Bu sınıfın fit metoduna biz veri kümesini bir bütün olarak versek bile fit aslında bu veri kümesinden satırları batch batch alıp işleme sokmaktadır. Ancak sınıfın asıl önemli özelliği partial_fit isimli bir metoda sahip olmasıdır. Bu sayede biz bir dosyadan satırları parça parça okuyup fit işlemi yapabiliriz. MiniBatchKMeans sınıfının __init__ metodunun parametrik yapısı şöyledir: class sklearn.cluster.MiniBatchKMeans(n_clusters=8, *, init='k-means++', max_iter=100, batch_size=1024, verbose=0, compute_labels=True, random_state=None, tol=0.0, max_no_improvement=10, init_size=None, n_init='auto', reassignment_ratio=0.01)[source] Buradaki batch_size veri kümesinden satırların kaçarlı çekilip işleme sokulacağını belirtmektedir. partial_fit metoduna burada belirtilen batch_size kadar satırın verilmesi gerekmektredir. MiniBatchKMeans nesnesi yaratıldıktan partial_fit işlemi yapılabilir. partial_fit metodunun parametrik yapısı şöyledir: partial_fit(X, y=None, sample_weight=None) "kmodes" ve "pyclustering" kütüphanelerindeki K-XXX algoritmalarında bir parçalı eğitim olanağı yoktur. Aşağıda daha önce üzerinde çalıştığımız "customer clustering" veri kümesi üzerinde parçalı eğitime ilişkin bir örnek verilmiştir. Bu örnekte veri kümesi parçalı bir biçimde iki kere dolaşılmıştır. Birinci dolaşımda nümerik sütunların özellik ölçeklemesi için ve kategorik sütunların one-hot-encoding işlemine sokulabilmesi için gerekli olan bilgiler toplanmöıştır. Kodun bu kısmı şöyledir: unique_list = [set(), set(), set()] count = 0 total = 0 total_square = 0 for df in pd.read_csv('segmentation data.csv', chunksize=BATCH_SIZE): unique_list[0].update(df['Education']) unique_list[1].update(df['Occupation']) unique_list[2].update(df['Settlement size']) total += df[numeric_features].sum() total_square += (df[numeric_features] ** 2).sum() count += df.shape[0] numeric_mean = total / count numeric_std = (total_square / count - numeric_mean ** 2) ** 0.5 unique_list = [list(us) for us in unique_list] Pandas'ın read_csv fonksiyonu chunksize parametresi ile kullanıldığında bize bir iteratör nesnesi verir. Bu iteratör her dolaşıldığında chunskize kadar elemandan oluşan DataFrame nesnesi elde edilmektedir. Tabii fonksiyon tüm dosyayı tek hamlede belleğe okuyarak bu işlemi yapmaz. Kendi içerisinde her defasında chunksize kadar kısmı okumaktadır. Örneğimizde daha sonra ikinci geçişte veriler parça parça okunup önişlemlere sokulup partial_firt metoduna verilmiştir: ohe = OneHotEncoder(sparse_output=False, categories=unique_list) for df in pd.read_csv('segmentation data.csv', chunksize=BATCH_SIZE): categoric_array = ohe.fit_transform(df[categoric_features]) numeric_array = ((df[numeric_features] - numeric_mean) / numeric_std).to_numpy() binary_array = df[binary_features].to_numpy() combined_array = np.hstack((categoric_array, binary_array, numeric_array)) mbkm.partial_fit(combined_array) Örnek aşağıda bir bütün olarak verilmiştir. #---------------------------------------------------------------------------------------------------------------------------- NCLUSTERS = 5 BATCH_SIZE = 10 import numpy as np import pandas as pd from sklearn.cluster import MiniBatchKMeans from sklearn.preprocessing import OneHotEncoder mbkm = MiniBatchKMeans(n_clusters=NCLUSTERS, batch_size=BATCH_SIZE) numeric_features = ['Age', 'Income'] categoric_features = ['Education', 'Occupation', 'Settlement size'] binary_features = ['Sex', 'Marital status'] unique_list = [set(), set(), set()] count = 0 total = 0 total_square = 0 for df in pd.read_csv('segmentation data.csv', chunksize=BATCH_SIZE): unique_list[0].update(df['Education']) unique_list[1].update(df['Occupation']) unique_list[2].update(df['Settlement size']) total += df[numeric_features].sum() total_square += (df[numeric_features] ** 2).sum() count += df.shape[0] numeric_mean = total / count numeric_std = (total_square / count - numeric_mean ** 2) ** 0.5 unique_list = [list(us) for us in unique_list] ohe = OneHotEncoder(sparse_output=False, categories=unique_list) for df in pd.read_csv('segmentation data.csv', chunksize=BATCH_SIZE): categoric_array = ohe.fit_transform(df[categoric_features]) numeric_array = ((df[numeric_features] - numeric_mean) / numeric_std).to_numpy() binary_array = df[binary_features].to_numpy() combined_array = np.hstack((categoric_array, binary_array, numeric_array)) mbkm.partial_fit(combined_array) print(mbkm.labels_) #---------------------------------------------------------------------------------------------------------------------------- Daha önce K-Means yönteminin K-XXX biçiminde isimlendirilen çeşitli varyasyonları olduğunu belirtmiştik. Bunları anımsayalım: K-Medoids K-Modes K-Prototypes K-Centers K-Medians K-Nearest Neighbors Şimdi bu yöntemlerin bazıları hakkında açıklamalar yapalım. #---------------------------------------------------------------------------------------------------------------------------- #---------------------------------------------------------------------------------------------------------------------------- K-Medoids yönteminde ana algoritma K-Means yöntemindeki gibidir. Ancak kümenin ağırlık merkezi o kümedeki noktaların boyutsal temelde ortalaması ile değil bu noktalardan bir tanesinin seçilmesiyle yapılmaktadır. Yani bu yöntemde her zaman kümenin ağırlık merkezi zaten var olan noktalardan biri olarak seçilir. ("Medoid" sözcüğü zaten İngilizce "bir grup verideki onu temsil eden bir tanesi" anlamına gelmektedir.) K-Means yönteminde kümeler için ağırlık merkezleri aslında küme içerisinde hiç bulunmayan bir nokta olarak elde edilmektedir. Ancak bu yöntemde küme içerisindeki noktalardan biri ağırlık merkezi olarak seçilir. Pekiyi küme içerisindeki hangi nokta en iyi ağırlık merkezi olmaya adaydır? İşte tipik olarak küme içerisindeki her noktanın ağırlık merkezi olduğunu varsayarak bu noktaya toplam uzaklık (ya da atalet) hesaplanır. Bu toplam uzaklığın en az olduğu nokta kümenin yeni ağırlık merkezi olarak seçilir. K-Medoids yöntemi "ağırlık merkezlerinin var olan noktalardan biri olması gerektiği durumlarda ve/veya aşırı uçta değerlerin (outliers) bulunduğu veri kümelerinde" tercih edilebilir. Ancak bu yöntem K-Means yöntemine göre daha fazla işlem zamanına gereksinim duymaktadır. (K-Means yönteminde yeni ağırlık merkezi için noktaların orta noktalarını hesaplamak O(N) karşıklıkta bir işlem olduğu halde nokta sayısı fazlalıştığında K-Medoids yönteminde ağırlık merkezi O(N^2) karmaşıklıkta hesaplanabilmektedir.) K-Medoids yöntemi doğrudan scikit-learn tarafından desteklenmemektedir. Ancak bu kütüphanenin "extra" paketinde KMedoids isimli bir sınıf bulunmaktadır. (scikit-learn-extra paketi scikit-learn kütüphanesinde bulunmayan bazı özelliklerin eklenmesiyle oluşturulmuş, onun eksiklerini kapatmayı hedefleyen bir kütüphanedir.) Tabii bunun için önce scikit-learn-extra paketi aşağıdaki gibi kurulmalıdır: pip install scikit-learn-extra K-Medoids sınıfının kullanımı KMeans sınıfının kullanımına çok benzerdir. K-Medoids yöntemi "pyclustering" isimli kütüphane içerisinde de gerçekleştirilmiştir. Bu kütüphaneyi de kullanabilirsiniz. Aşağıda "zambak (iris)" veri kümesi üzerinde K-Medoids yönteminin uygulandığı bir kod verilmiştir. #---------------------------------------------------------------------------------------------------------------------------- NCLUSTERS = 3 import pandas as pd df = pd.read_csv('iris.csv') dataset = df[['SepalLengthCm','SepalWidthCm','PetalLengthCm','PetalWidthCm']].to_numpy('float32') from sklearn.preprocessing import StandardScaler ss = StandardScaler() ss.fit(dataset) transformed_dataset = ss.transform(dataset) from sklearn_extra.cluster import KMedoids km = KMedoids(n_clusters=NCLUSTERS) km.fit(transformed_dataset) df['Cluster'] = km.labels_ import matplotlib.pyplot as plt from sklearn.decomposition import PCA pca = PCA(n_components=2) pca.fit(dataset) reduced_dataset = pca.transform(dataset) reduced_centroids = pca.transform(km.cluster_centers_) plt.title('Clustered Points', fontsize=12) for i in range(NCLUSTERS): plt.scatter(reduced_dataset[km.labels_ == i, 0], reduced_dataset[km.labels_ == i, 1]) plt.scatter(reduced_dataset[km.medoid_indices_, 0], reduced_dataset[km.medoid_indices_, 1], color='red') 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='float32') transformed_predict_data = ss.transform(predict_data) predict_result = km.predict(transformed_predict_data) print(predict_result) #---------------------------------------------------------------------------------------------------------------------------- 87. Ders - 15/12/2024 - Pazar #---------------------------------------------------------------------------------------------------------------------------- #---------------------------------------------------------------------------------------------------------------------------- K-Medians yöntemi de K-Means yöntemi gibidir. Ancak kümeler oluşturulduktan sonra kümelerin ağırlık merkezleri sütun ortalamaları ile değil sütunlarım median değerleriyle yapılmaktadır. Böylece uç değerlerin ortalamadaki olumsuz etkisi giderilmiş olur. Tabii median bulma bir sıraya dizme gerektirdiği için daha fazla zaman alan bir işlemdir. Bu yöntem ancak uç değerlerin bulunduğu ve bunların veri kümelerinden atılmadığı durumlarda tercih edilebilecek bir yöntemdir. Ayrıca K-Medians yönteminde noktaların biribirine uzaklığını hesaplamak için genellikle Öklit uzaklığı yerine Manhattan uzaklığı tercih edilmektedir. Manhattan uzaklığı medyan işlemiyle daha uyumludur. K-Medians yöntemi sckit-learn kütüphanesi tarafından gerçekleştirilmemiştir. Bunun için "pyclustering" kütüphanesi kullanılabilir. Bu kütüphaneyi şöyle kurabilirsiniz: pip install pyclustering Bu kütüphane kullanılarak K-Medians uygulamasına yönelik bir örnek aşağıda verilmiştir. #---------------------------------------------------------------------------------------------------------------------------- import pandas as pd from pyclustering.cluster.kmedians import kmedians df = pd.read_csv('points.csv') dataset = df.to_numpy(dtype='float32') km = kmedians(dataset, initial_medians=[[5, 4], [1, 2]]) km.process() clusters = km.get_clusters() print(clusters) #---------------------------------------------------------------------------------------------------------------------------- K-Modes tüm sütunların kategorik ölçekte olduğu veri kümelerinde kullanılabilen bir kümeleme yöntemidir. Örneğin aşağıdaki gibi bir veri kümesi olsun: İndex Renk Cinsiyet Ülke ------------------------------------ 0 Kırmızı Kadın Türkiye 1 Mavi Erkek Almanya 2 Yeşil Kadın Fransa 3 Mavi Kadın İngiltere 4 Kırmızı Erkek Türkiye 5 Yeşil Erkek Almanya 6 Kırmızı Kadın Fransa 7 Mavi Erkek Türkiye 8 Yeşil Kadın İngiltere 9 Mavi Kadın Almanya Bu veri kümesinde biz kümeleme yapmak isteyelim. K-Means bunun için uygun değildir. Çünkü K-Means Öklit uzaklığını kullanır. Buradaki kategorilerin Öklit uzaklığına dönüştürülmesi anlam kaybına yol açacaktır. İşte sütunların hepsinin kategorik olduğu durumlarda K-Means yerine K-Modes yöntemi tercih edilmelidir. K-Modes algoritmasının ana işleyişi K-Means gibidir. Ancak uzaklık hesaplamaları "Hamming uzaklığı" kullanılarak yapılır. Anımsanacağı gibi Hamming uzaklığı "farklı olanların ortalaması" ile hesaplanmaktadır. Ancak burada Hamming uzaklığı için ortalama almaya gerek yoktur. Farklı olanların sayısının elde edilmesi yeterlidir. K-Modes algoritmasında yine başlangıçta kaç kümenin oluşturulacağı uygulamacı tarafından belirlenmelidir. Örneğin biz yukarıdaki veri kümesi için 2 kümenin oluşturulmasını isteyelim. Küme sayısı belirlendikten sonra küme sayısı kadar rastgele nokta alınmaktadır. Bu iki reastgele nokta şunlar olsun: 0'ıncı küme için rastgele ağırlık merkezi ===> Yeşil Kadın Fransa 1'inci küme için rastgele ağırlık merkezi ===> Mavi Erkek Türkiye Bundan sonra K-Means algoritmasında olduğu gibi tüm noktaların bu iki noktaya Hamming uzaklıklarını hesaplayıp bu noktalar hangisine yakınsa o kümeye dahil etmektir. Örneğin ilk elemanın ("Kırmızı Kadın Türkiye") her iki noktaya Hamming uzaklığını hesaplayalım: "Kırmızı Kadın Türkiye" ile "Yeşil Kadın Fransa" arasındaki Hamming uzaklığı 2'dir. "Kırmızı Kadın Türkiye" ile "Mavi Erkek Türkiye" arasındaki Hamming uzaklığı 2'dir. "Kırmızı Kadın Türkiye" noktasının her iki noktaya Hamming uzaklığı aynı olduğuna göre biz bu noktayı bu kümelerden herhangi birine dahil edebiliriz. Şimdi "Mavi Erkek Almanya" noktasının iki noktaya Hamming uzaklıklarını hesaplayalım: "Mavi Erkek Almanya" ile "Yeşil Kadın Fransa" arasındaki Hamming uzaklığı 3'tür. "Mavi Erkek Almanya" ile "Mavi Erkek Türkiye" arasındaki Hamming uzaklığı 1'dir. O halde bu nokta 1 numaralı kümeye atanmalıdır. İşte böyle her nokta Hamming uzaklığı temelinde bir kümeye atanır. Bunun sonucunda ilk kümeleme yapılmış olur. Bundan sonra K-Means yönteminde olduğu gibi kümelerin gerçek ağırlık merkezleri kendi elemanlarına göre belirlenmelidir. K-Means yönteminde biz kümedeki elemanların sütunsal ortalamaları ile yeni ağırlık merkezini buluyorduk. Sütunlar kategorik olduğuna göre K-Modes yönteminde biz her sütunun ortalama yerine mod'unu alarak yeni ağırlık merkezini buluruz. Örneğin kümelerden biri şu noktalara sahip olsun: Mavi Erkek Türkiye Mavi Kadın Almanya Yeşil Erkek Türkiye Kırmızı Erkek Türkiye Buradaki yeni ağırlık merkezleri şöyle oluşturulacaktır: Mavi Erkek Türkiye Görüldüğü gibi nasıl K-Means yönteminde her sütunun ortalaması alınarak yeni ağırlık merkezleri bulunuyorsa K-Modes yönteminde her sütunun mod değeri alınarak ortalama bulunmaktadır. K-Means ismi nasıl "ortalama almakla yeni ağırlık merkezinin bulbulunmasından" geliyorsa K-Modes ismi de "mod alarak yeni ağırlık merkezinin bulunmasından" gelmektedir. #---------------------------------------------------------------------------------------------------------------------------- #---------------------------------------------------------------------------------------------------------------------------- Pekiyi biz tüm sütunların kategorik olduğu durumda en uygun küme sayısını nasıl belirleyebiliriz? Bunun için dirsek yöntemi kullanılabilir ancak dirsek yöntemi pek uygun bir yöntem değildir. Ancak silüet yöntemi "hamming uzaklığı temelinde" uygulanabilir. scikit-learn içerisindeki silhouette_score fonksiyonun parametrik yapısını hatırlayınız: sklearn.metrics.silhouette_score(X, labels, *, metric='euclidean', sample_size=None, random_state=None, **kwds) Fonksiyonun metric parametresi "hamming" geçilirse fonksiyon silüet yöntemini "hamming uzaklığını" kullanarak uygulayacaktır. Ancak kategorik verilerin de önce LabelEncoder sınıfı ile sayısal biçime dönüştrülmesi gerekmektedir. #---------------------------------------------------------------------------------------------------------------------------- #---------------------------------------------------------------------------------------------------------------------------- K-Modes yöntemi scikit-learn kütüphanesinde gerçekleştirilmemiştir. scikit-learn extra paketi içerisinde de gerçekleştirimi yoktur. Ancak bu yöntemin "kmodes" kütüpanesinde ve "pyclustering" kütüphanesinde gerçekleştirimi bulunmaktadır. Biz burada "kmodes" kütüphanesini kullanarak bir örnek vereceğiz. "kmodes" kütüphanesinin kurulumu şöyle yapılabilir: pip install kmodes Kütüphane içerisinde K-Modes yöntemi KModes isimli sınıf kullanılarak uygulanmaktadır. Sınıfın kullanımı tamamen scikit-learn'deki KMeans sınıfının kullanımına benzetilmiştir. Sınıfın __init__ metodunun parametrik yapısı şöyledir: KModes(n_clusters=8, max_iter=100, cat_dissim=matching_dissim, init='Cao', n_init=10, verbose=0, random_state=None, n_jobs=1): Yine önce KModes sınıfı türünden nesne yaratılır. Sonra fit işlemi yapılır. Kümeleme işlemi sonucunda elde edilen bilgiler yine nesnenin özniteliklerinden elde edilir. Örneğin nesnenin labels_ özniteliğinden biz hangi noktaların hangi kümelere atandığını belirleyebiliriz. KModes sınıfının kullanımına şöyle bir örnek verebiliriz: dataset_dict = { 'Renk': ['Kırmızı', 'Mavi', 'Yeşil', 'Mavi', 'Kırmızı', 'Yeşil', 'Kırmızı', 'Mavi', 'Yeşil', 'Mavi'], 'Cinsiyet': ['Kadın', 'Erkek', 'Kadın', 'Kadın', 'Erkek', 'Erkek', 'Kadın', 'Erkek', 'Kadın', 'Kadın'], 'Ülke': ['Türkiye', 'Almanya', 'Fransa', 'İngiltere', 'Türkiye', 'Almanya', 'Fransa', 'Türkiye', 'İngiltere', 'Almanya'] } dataset_df = pd.DataFrame(dataset_dict) km = KModes(n_clusters=3, n_init=10) km.fit(dataset_df) cluster_0 = dataset_df.iloc[km.labels_ == 0] cluster_1 = dataset_df.iloc[km.labels_ == 1] cluster_2 = dataset_df.iloc[km.labels_ == 2] Burada veri kümesi K-Modes yöntemiyle üç kümeye ayrıştırılmıştır. En uygun küme sayısını da silüet yöntemiyle şöyle belirleyebiliriz: le = LabelEncoder() encoded_df = pd.DataFrame() for column in dataset_df.columns: encoded_df[column] = le.fit_transform(dataset_df[column]) optimal_cluster = np.argmax([silhouette_score(encoded_df, KModes(i, n_init=10).fit(dataset_df).labels_, metric='hamming') for i in range(2, 8)]) + 3 Aşağıdaki örnekte önce en uygun küme sayısı tespit edilip sonra KModes sınıfı kullanılarak K-Modes kümeleme işlemi yapılmıştır. #---------------------------------------------------------------------------------------------------------------------------- import pandas as pd dataset_dict = { 'Renk': ['Kırmızı', 'Mavi', 'Yeşil', 'Mavi', 'Kırmızı', 'Yeşil', 'Kırmızı', 'Mavi', 'Yeşil', 'Mavi'], 'Cinsiyet': ['Kadın', 'Erkek', 'Kadın', 'Kadın', 'Erkek', 'Erkek', 'Kadın', 'Erkek', 'Kadın', 'Kadın'], 'Ülke': ['Türkiye', 'Almanya', 'Fransa', 'İngiltere', 'Türkiye', 'Almanya', 'Fransa', 'Türkiye', 'İngiltere', 'Almanya'] } dataset_df = pd.DataFrame(dataset_dict) import numpy as np from sklearn.metrics import silhouette_score from kmodes.kmodes import KModes from sklearn.preprocessing import LabelEncoder le = LabelEncoder() encoded_df = pd.DataFrame() for column in dataset_df.columns: encoded_df[column] = le.fit_transform(dataset_df[column]) optimal_cluster = np.argmax([silhouette_score(encoded_df, KModes(i, n_init=10).fit(dataset_df).labels_, metric='hamming') for i in range(2, 8)]) + 3 km = KModes(n_clusters=optimal_cluster, n_init=10) km.fit(dataset_df) for i in range(optimal_cluster): cluster = dataset_df.iloc[km.labels_ == 0] print(f'cluster {i}') print(cluster) print('-' * 10) #---------------------------------------------------------------------------------------------------------------------------- #---------------------------------------------------------------------------------------------------------------------------- 88. Ders - 21/12/2024 - Cumartesi #---------------------------------------------------------------------------------------------------------------------------- #---------------------------------------------------------------------------------------------------------------------------- K-Prototypes yöntemi hem sayısal hem de kategorik sütunlara sahip olan veri kümeleri için tercih edilen kümeleme yöntemlerinden biridir. Biz daha önce bu tür karma (mixed) veri kümeleri için K-Means yönteminin de kullanılabileceğini belirtmiştik. Ancak K-Means yöntemi uygulanmadan önce kategorik sütunların one-hot-encoding gibi bir yolla yoluyla sayısallaştırılması gerekiyordu. One-hot-encoding yoluyla sayıllaştırmanın bazı dezavantajlarından da bahsetmiştik. K-Prototypes yönteminde önce sayısal sütunlarla kategorik sütunlar birbirlerinden ayrıştırılır. Ağırlık merkezi sayısal sütunların ortalaması kategorik sütunların ise mod'ları elde edilerek oluşturulur. Örneğin aşağıdaki gibi bir karma veri kümesi söz konusu olsun: Yaş Gelir (Bin TL) Eğitim Süresi Cinsiyet Şehir --------------------------------------------------------------------- 25 45.3 5 Kadın İstanbul 34 52.1 12 Erkek Ankara 29 63.5 8 Kadın İzmir 41 58.7 15 Erkek İstanbul 23 49.4 4 Kadın Antalya 37 54.8 10 Erkek İstanbul 30 61.3 9 Kadın İzmir 28 57.9 7 Erkek Ankara 26 50.2 6 Kadın Antalya 35 62.4 11 Erkek İzmir Buradaki Yaş, Gelir ve Eğitim Süresi sütunları sayısal, Cinsiyet ve Şehir sütunları ise kategorik bilgiler içermektedir. Şimdi kümeleme işlemi sırasında oluşturulan bir kümenin noktalarının aşağıdaki gibi olduğunu varsayalım: 37 54.8 10 Erkek İstanbul 26 50.2 6 Kadın Antalya 29 63.5 8 Kadın İzmir 41 58.7 15 Erkek İstanbul 35 62.4 11 Erkek İzmir Bu noktaların ağırlık merkezleri oluşturulurken sayısal sütunların ortalamaları, kategorik sütunların ise modları elde edilmektedir. Böylece aşağıdaki gibi bir ağırlık merkezi oluşacaktır: 33.6 57.92 10 Erkek İstanbul Bu biçimde elde edilen ağırlık merkezine "prototip (prototype)" de denilmektedir. K-Prototypes algoritmasında da yine başlangıçta küme sayısı kadar rastgele ağırlık merkezleri elde edilir. (Bu noktalar genellikle var olan noktalardan seçilmektedir.) Sonra her noktanın bu ağırlık merkezlerine uzaklığı hesaplanır. Sonra yine noktalar kendilerine en yakın ağırlık merkezlerine ilişkin kümelere atanır. Pekiyi K-Prototypes yönteminde iki nokta arasındaki uzaklık nasıl hesaplanmaktadır? Örneğin aşağıdaki gibi iki nokta arasındaki uzaklık nasıl hesaplanacak olsun: 30 61.3 9 Kadın İzmir 33.6 57.92 10 Erkek İstanbul Eğer sütunların hepsi sayısal olsaydı biz Öklit uzaklığını kullanırdık. Sütunların hepsi kategorik olsaydı bu durumda da Hamming uzaklığını kullanırdık. Ancak burada bazı sütunları nümerik olan bazı sütunları kategorik olan bir veri kümesi söz konusudur. İşte bu tür karma sütunların bulunduğu durumlarda iki nokta arasındaki uzaklık da karma bir biçimde yani nümerik uzaklıklarla kategorik uzaklıkların toplamı biçiminde hesaplanmaktadır. Hesaplama tipik olarak şöyle yapılmaktadır: Uzaklık = Nümerik sütunların uzaklığı + gamma * kategorik sütunların uzaklığı Sayısal sütunların uzaklığı için Öklit uzaklığı, kategorik sütunların uzaklığı için Hamming uzaklığı kullanılabilir. Buradaki "gamma" deneme yanılma yoluyla ya da bazı sezgisel yaklaşımlarla belirlenecek olan iki tür sütunun uyumlandırılmasını sağlayan çarpansal bir değerdir. Tabii bu uzaklık hesabı yapılmadan önce sayısal sütunlar özellik ölçeklemesine sokulmalıdır. Ölçekleme için standart ölçekleme ya da min-max ölçeklemesi kullanılabilir. Pekiyi yukarıdaki uzaklık hesabında gamma çarpanının deneme yanıl deneme yanılma yoluyla nasıl tespit edilebilir? Bunun için çeşitli gamma değerleriyle kümeleme yapılıp bunlar arasından en iyi kümelemeye lişkin gamma değeri seçilebilir. Ancak gamma değerinin deneme yanılma yöntemi kullanılmadan seçilmesine yönelik deb çeşitli yaklaşımlar bulunmaktadır. Bu yaklaşımların bazıları gamma değerini sayısal sütunların standart sapmalarının ortalamalarına dayalı olarak belirlemektedir. Örneğin "kmodes" kütüphanesindeki KPrototypes sınıfında gamma değeri programcı tarafından belirtilmediyse default olarak aşağıdaki gibi elde edilmektedir: gamma = 0.5 * np.mean(Xnum.std(axis=0)) Burada Xnum nümerik sütunları belirtmektedir. Burada gamma değeri sayısal sütunların standart sapmalarının ortalamasının yarısı biçiminde elde edilmiştir. #---------------------------------------------------------------------------------------------------------------------------- #---------------------------------------------------------------------------------------------------------------------------- K-Prototypes yöntemi scikit-learn içerisinde gerçeklkeştirilmemiştir. Ancak bu yöntemin "kmodes" kütüphanesinde ve "pyclustering" kütüphanelerinde gerçekleştirimi bulunmaktadır. Biz burada sciklit-learn kütüphanesine benzer bir kullanıma sahip olduğu için "kmodes" kütüphanesindeki gerçekleştirim için bir örnek vereceğiz. "kmodes" kütüphanesindeki KPrototypes sınıfı öyle kullanılmaktadır: 1) Önce veri kümesindeki sayısal ve kategorik sütunlar belirlenir. 2) Nümerik sütunlar özellik ölçeklemesine sokulur. 3) Kategorik sütunlar LabelEncoder sınıfı ile sayısallaştırılır. Bu işlemler sonucunda dataset isimli bir NumPy dizisinin elde edildiğini varsayalım. 4) Şimdi KPrototypes nesnesi oluşturulur. Örneğin: kp = KPrototypes(n_clusters=5) 5) fit işlemi yapılır. fit işleminde tüm veri kümesi ve kategorik sütunların indeksleri categorical paraetresiyle metoda verilir. Örneğin: kp.fit(dataset, categorical=[0, 1, 3, 5, 6]) Artık sınıfın labels_ örnek özniteliğinden hangi satırların hangi kümeye atandığı bilgisini elde edebiliriz. Yine nesnenin cluster_centroids_ özniteliğinden kümelerin ağırlık merkezleri elde edilebilmektedir. Aşağıda KPrototypes sınıfının kullanımına ilişkin bir örnek verilmiştir. #---------------------------------------------------------------------------------------------------------------------------- NCLUSTERS = 5 import pandas as pd df = pd.read_csv('segmentation data.csv') df.drop(labels=['ID'], axis=1, inplace=True) from sklearn.preprocessing import StandardScaler ss = StandardScaler() df[['Age', 'Income']] = ss.fit_transform(df[['Age', 'Income']]) from sklearn.preprocessing import LabelEncoder le = LabelEncoder() for column in ['Sex', 'Marital status', 'Education', 'Occupation', 'Settlement size']: df[column] = le.fit_transform(df[column]) dataset = df.to_numpy() from kmodes.kprototypes import KPrototypes kp = KPrototypes(n_clusters=NCLUSTERS) kp.fit(dataset, categorical=[0, 1, 3, 5, 6]) for i in range(NCLUSTERS): print(f'{i}. Cluster points', end='\n\n') print(df.iloc[kp.labels_ == i, :]) print('-' * 20, end='\n\n') #---------------------------------------------------------------------------------------------------------------------------- 89. Ders - 22/12/2024 - Pazar #---------------------------------------------------------------------------------------------------------------------------- #---------------------------------------------------------------------------------------------------------------------------- MiniBatchKMeans kullanımına ilişkin bir örnek yapılmıştır. Bu örnek yukarıya eklenmiştir. #---------------------------------------------------------------------------------------------------------------------------- #---------------------------------------------------------------------------------------------------------------------------- Şimdi de MNIST veri kümesine K-Means yöntemini uygulayalım. Bunu şöyle bir problemi çözmek için yapıyor olabiliriz: Birisi bize 28x28'lik binlerce resim versin. Bu resimleri veren kişi bu resimlerin 10 farklı nesneye ilişkin olduğunu bize söylesin. Resimleri veren kişinin amacı bunların otomatik bir biçimde ayrıştırılmasını sağlamak olsun. Biz de K-Means yöntemiyle bunu yapmaya çalışalım. Bilindiği gibi aslında resimler pixellerden pixeller de sayılardan oluşmaktadır. MNIST resimlerinin "gri tonlamalı (gray scale) olduğunu anımsayınız. (Yani resimlerdeki her pixel 1 byte'lık bir sayı ile temsil edilmektedir.) O halde resimler toplamda 28 * 28 = 784 tane nümerik sütundan oluşan noktalar gibi ele alınabilir. #---------------------------------------------------------------------------------------------------------------------------- NCLUSTERS = 10 from tensorflow.keras.datasets import mnist (training_dataset_x, training_dataset_y), (test_dataset_x, test_dataset_y) = mnist.load_data() scaled_training_dataset_x = training_dataset_x.reshape(-1, 28 * 28) / 255 scaled_test_dataset_x = test_dataset_x.reshape(-1, 28 * 28) / 255 from sklearn.cluster import KMeans km = KMeans(n_clusters=NCLUSTERS, n_init=10) km.fit(scaled_training_dataset_x) from sklearn.decomposition import PCA pca = PCA(n_components=2) transformed_dataset_x = pca.fit_transform(scaled_training_dataset_x) import matplotlib.pyplot as plt plt.figure(figsize=(8, 6)) plt.title('Clustered Points') for i in range(NCLUSTERS): plt.scatter(transformed_dataset_x[km.labels_ == i, 0], transformed_dataset_x[km.labels_ == i, 1]) plt.show() # etiketlendirmenin örnek sonuçları import matplotlib.pyplot as plt for cluster_no in range(10): plt.figure(figsize=(6, 10)) print(f'Cluster No: {cluster_no}') for i, picture in enumerate(training_dataset_x[km.labels_ == cluster_no][:50]): plt.subplot(13, 5, i + 1) plt.imshow(picture, cmap='gray') plt.show() print('-' * 30) #---------------------------------------------------------------------------------------------------------------------------- K-Means yöntemi uç değerlerden oldukça etkilenmektedir. Bu nedenle uygulamacının verilerdeki uç değerleri (outliers) temizlemesi uygun olur. İzleyen paragraflarda uç değerlerden daha az etkilenecek yöntemler göreceğiz. K-Means algoritma karmaşıklığı bakımından hızlı bir yöntemdir. K-Means yönteöminin yalnızca Öklit uzaklığını kullanması da bir dezavantaj olarak değerlendirilebilir. K-Means yönteminde en önemli noktalardan biri başlangıçta kümelerin ağırlık merkezlerinin nasıl seçileceğidir. Ayrıca bu yöntemde uygulamacının küme sayısını işin başında kendisinin tespit etmiş olması gerekmektedir. İş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. Zaten scikit-leran kütüphanesindeki KMeans sınıfı default olarak bu yöntemi kullanmaktadır. #---------------------------------------------------------------------------------------------------------------------------- #---------------------------------------------------------------------------------------------------------------------------- Çok kullanılan diğer bir kümeleme yöntem grubu da "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. Agglomerative algoritmalar "aşağıdan yukarı (bottom-up)", divise algoritmalar ise "yukarıdan aşağıya (top-down)" doğru işlem yapmaktadır. 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 aşağıdaki gibi 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 simetrik bir 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 arasındaki uzaklıklar hesaplanır. Ancak iki elemanlı küme sanki tek bir nokta gibi değerlendirilecektir. Bu aşamadan sonra yeniden bir birleştirme yapılır. Böylece n - 2 tane nokta elde edilir. İşlemler istenen k tane küme elde edilene kadar devam ettirilir. Algoritmadaki önemli noktalar şunlardır: - 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 ele alınmaktadır? Bu durumda bu kümeye olan 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 algoritmasının gerçekleştirilmesi zor değildir. Tüm noktaların diğer noktalara uzaklığı iç içe iki döngü ile hesaplanabilir. Tabii algoritmada kümeleri daraltan dış bir döngü de bulunmak zorundadır. Bu durumda algoritma karmaşıklığı O(n^3) halline gelebilmektedir. Algoritmada ek birtakım önlemler alınarak algoritima O(n^2) karmaşıklığına yaklaştırılabilmektedir. Aşağıda ChatGPT ile üretilen (ancak prompt'larla yönlendirme de yapılmıştır) agglomerative hiyearşik kümeleme algoritmasına bir örnek verilmiştir. #---------------------------------------------------------------------------------------------------------------------------- import numpy as np def euclidean_distance(a, b): """ Compute the Euclidean distance between two points a and b. """ return np.sqrt(np.sum((a - b) ** 2)) def calculate_pairwise_distances(X): """ Calculate the pairwise Euclidean distance between all data points. Parameters: X : ndarray of shape (n_samples, n_features) The input data points. Returns: distance_matrix : ndarray of shape (n_samples, n_samples) A matrix containing pairwise distances. """ n_samples = X.shape[0] distance_matrix = np.zeros((n_samples, n_samples)) # Compute the pairwise Euclidean distance between all points for i in range(n_samples): for j in range(i + 1, n_samples): distance = euclidean_distance(X[i], X[j]) distance_matrix[i, j] = distance distance_matrix[j, i] = distance return distance_matrix def update_distances(distance_matrix, clusters, new_cluster_index): """ Update the distance matrix after merging two clusters. Parameters: distance_matrix : ndarray of shape (n_samples, n_samples) The distance matrix. clusters : dict A dictionary of clusters with cluster ids as keys and lists of data points as values. new_cluster_index : int The index of the newly formed cluster. Returns: distance_matrix : ndarray Updated distance matrix. """ # Remove the distances related to the merged cluster distance_matrix = np.delete(distance_matrix, new_cluster_index, axis=0) distance_matrix = np.delete(distance_matrix, new_cluster_index, axis=1) # Recalculate the distances for the new merged cluster new_distances = [] for cluster_id in clusters: if cluster_id != new_cluster_index: # Calculate the average linkage: distance between clusters by average dist = np.mean([distance_matrix[idx1, idx2] for idx1 in clusters[cluster_id] for idx2 in clusters[new_cluster_index]]) new_distances.append(dist) # Add the new distances to the distance matrix new_row = np.array(new_distances) distance_matrix = np.vstack([distance_matrix, new_row]) new_col = np.append(new_row, 0).reshape(-1, 1) distance_matrix = np.hstack([distance_matrix, new_col]) return distance_matrix def agglomerative_hierarchical_clustering(X, n_clusters=2): """ Perform Agglomerative Hierarchical Clustering using average linkage. Parameters: X : ndarray of shape (n_samples, n_features) The input data points. n_clusters : int The number of clusters to form. Returns: labels : ndarray of shape (n_samples,) Cluster labels for each data point. """ # Step 1: Calculate the pairwise distance matrix distance_matrix = calculate_pairwise_distances(X) # Step 2: Initialize each point as its own cluster clusters = {i: [i] for i in range(X.shape[0])} # Use a dictionary for clusters # Step 3: Merge clusters until the number of clusters equals n_clusters while len(clusters) > n_clusters: # Step 3.1: Find the closest pair of clusters (minimum distance) min_dist = np.inf cluster_pair = None for i in clusters: for j in clusters: if i < j: # To avoid checking the same pair twice # Calculate distance between clusters using average linkage dist = np.mean([distance_matrix[idx1, idx2] for idx1 in clusters[i] for idx2 in clusters[j]]) # Update the minimum distance and the pair of clusters if dist < min_dist: min_dist = dist cluster_pair = (i, j) # Step 3.2: Merge the two closest clusters i, j = cluster_pair # Merge clusters by updating the dictionary clusters[i] = clusters[i] + clusters[j] # Merge clusters del clusters[j] # Remove the merged cluster # Step 3.3: Update the distance matrix after merging the clusters distance_matrix = update_distances(distance_matrix, clusters, j) # Step 4: Assign labels to data points based on final clusters labels = np.zeros(X.shape[0]) for cluster_id, cluster in clusters.items(): for idx in cluster: labels[idx] = cluster_id return labels # Example usage np.random.seed(42) X = np.random.rand(10, 2) # 10 data points with 2 features # Perform Agglomerative Hierarchical Clustering with average linkage labels = agglomerative_hierarchical_clustering(X, n_clusters=3) # Print the resulting cluster labels print("Cluster labels:", labels) #---------------------------------------------------------------------------------------------------------------------------- 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: class sklearn.cluster.AgglomerativeClustering(n_clusters=2, *, metric='euclidean', 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 kaldırılmış bunun yerine "metric" parametresi kullanılmaya başlanmıştır. (Yani bu anlamda terminolojide bir değişiklik yapılmıştır.) Eğer sklearn veriyonunuz 1.4'ten geri ise metric yerine affinity parametresini kullanınız. metrik parametresi uzaklık hesaplama yöntemini belirtmektedir. Bu parametrenin default değerinin "euclidean" olduğunu görüyorsunuz. linkage parametresi kümeye ilişkin noktaların temsil edildiği noktanın nasıl belirleneceğini belirlemek için kullanılmaktadır. Yani bu parametre eğer bir küme birden fazla nokta içeriyorsa bu kümenin tek nokta gibi ele alınabilmesi için hangi hesaplama yönteminin kullanılacağını belirtmektedir. Bu parametreye şu değerlerden biri girilebilir: "ward", "average", "complete (ya da maximum)", ve "single". Bu parametrenin default değeri "ward" biçimindedir. Bu durum kümenin tüm noktalarına uzaklıklarının karelerinin ortalaması yönteminin kullanılacağını belirtir. "average" grup ortalaması anlamına, "complete ya da maximum" maksimum uzaklık anlamına "single" ise minimum uzaklık anlamına gelir. Metodun compute_distances parametresi default durumda False biçimdedir. eğer bu parametre True geçilirse bu durumda fit işlemi sonrasında nesnede noktaların uzaklığına ilişkin bilgi veren distances_ özniteliği oluşturulmaktadır. Metodun diğer parametrelerini dokümanlardan inceleyebilirsiniz. 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: n_clusters_: Elde edilen kü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ı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. Bu uzaklık değerleri dendrogram çizerken kullanılabilmektedir. 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 pandas as pd df = pd.read_csv('points.csv') dataset = df.to_numpy(dtype='float32') from sklearn.cluster import AgglomerativeClustering ac = AgglomerativeClustering(n_clusters=NCLUSTERS, linkage='ward', compute_distances=True) ac.fit(dataset) df['Cluster'] = ac.labels_ import matplotlib.pyplot as plt for i in range(NCLUSTERS): plt.scatter(dataset[ac.labels_ == i, 0], dataset[ac.labels_ == i, 1]) plt.show() #---------------------------------------------------------------------------------------------------------------------------- 91. Ders - 05/01/2025 - Pazar #---------------------------------------------------------------------------------------------------------------------------- #---------------------------------------------------------------------------------------------------------------------------- Şimdi de 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 birbirne 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[['SepalLengthCm','SepalWidthCm','PetalLengthCm','PetalWidthCm']].to_numpy('float32') from sklearn.preprocessing import StandardScaler ss = StandardScaler() ss.fit(dataset) transformed_dataset = ss.transform(dataset) from sklearn.decomposition import PCA pca = PCA(n_components=2) pca.fit(dataset) reduced_dataset = pca.transform(dataset) from sklearn.cluster import AgglomerativeClustering ac = AgglomerativeClustering(n_clusters=NCLUSTERS) ac.fit(transformed_dataset) import matplotlib.pyplot as plt plt.title('Agglomerative Clustered Points', fontsize=12) for i in range(NCLUSTERS): plt.scatter(reduced_dataset[ac.labels_ == i, 0], reduced_dataset[ac.labels_ == i, 1]) plt.show() from sklearn.cluster import KMeans km = KMeans(n_clusters=NCLUSTERS, n_init=10) km.fit(transformed_dataset) transformed_centroids = ss.inverse_transform(km.cluster_centers_) reduced_centroids = pca.transform(transformed_centroids) plt.title('K-Means Clustered Points', fontsize=12) for i in range(NCLUSTERS): plt.scatter(reduced_dataset[km.labels_ == i, 0], reduced_dataset[km.labels_ == i, 1]) plt.scatter(reduced_centroids[:, 0], reduced_centroids[:, 1], color='red', marker='s') plt.show() #---------------------------------------------------------------------------------------------------------------------------- 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 labels_ örnek özniteliği ile geri dönmektedir. 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_ Aşağıda fit_predict metodunun kullanımına bir örnek verilmiştir. #---------------------------------------------------------------------------------------------------------------------------- NCLUSTERS = 3 import pandas as pd df = pd.read_csv('points.csv') dataset = df.to_numpy(dtype='float32') from sklearn.cluster import AgglomerativeClustering ac = AgglomerativeClustering(n_clusters=NCLUSTERS, linkage='ward', compute_distances=True) ac.fit(dataset) df['Cluster'] = ac.labels_ import matplotlib.pyplot as plt for i in range(NCLUSTERS): plt.scatter(dataset[ac.labels_ == i, 0], dataset[ac.labels_ == i, 1]) plt.show() #---------------------------------------------------------------------------------------------------------------------------- scikit-learn içerisinde kümeleme ve sınıflandırma işlemleri için rastgele veri üreten bazı fonksiyonlar da oluşturulmuştur. Bunlar sklearn.datasets modülü içerisindedir. make_blobs fonksiyonu belli merkezlerden 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 üretilecek noktaların sayısını, n_features parametresi üretilecek rastgele verilerin kaç sütundan oluşacağını belirtmektedir. (Başka bir deyişle n_features kaç boyutlu uzay için nokta üretileceğini belirtmektedir.) centers parametresi etiket 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ılır. Bu değer küçültülürse noktalar kendi merkezlerine daha yakın, büyütülürse kendi merkezlerinden daha uzak olabilecek biçimde üretilmektedir. center_box parametresi ikili bir demet almaktadır. Rastgele ü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 edilecektir. Fonksiyon bize normal olarak iki elemanlı NumPy dizilerinden oluşan bir demet vermektedir. Bu demetin birinci elemanı üretilmiş olan rastgele noktaları, ikinci elemanı ise onların sınıflarını belirtmektedir. Eğer fonksiyonda return_centers parametresi True girilirse bu durumda fonksiyon üçlü bir demete geri döner. Demetin üçüncü elemanı kümelere ilişkin merkez noktalarını belirtir. Aşağıda make_blobs fonksiyonu ile 3 merkezli (default durumda da zaten 3 merkez alınıyor) rastgele noktalar oluşturulup grafiği çizdirilmiştir. #---------------------------------------------------------------------------------------------------------------------------- from sklearn.datasets import make_blobs dataset, labels = make_blobs(100, 3, cluster_std=1, centers=3) import matplotlib.pyplot as plt plt.title('make_blobs Sample points') plt.scatter(dataset[:, 0], dataset[:, 1]) plt.show() #---------------------------------------------------------------------------------------------------------------------------- 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, _ = make_blobs(100, 3, cluster_std=1, centers=NCLUSTERS) from sklearn.cluster import KMeans, AgglomerativeClustering km = KMeans(NCLUSTERS, n_init=10) km.fit(dataset) ac = AgglomerativeClustering(NCLUSTERS) ac.fit(dataset) import matplotlib.pyplot as plt plt.title('Agglomerative Clustering') for i in range(NCLUSTERS): plt.scatter(dataset[ac.labels_ == i, 0], dataset[ac.labels_ == i, 1]) plt.show() plt.title('K-Means Clustering') for i in range(NCLUSTERS): plt.scatter(dataset[km.labels_ == i, 0], dataset[km.labels_ == i, 1]) 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ı belirtir. 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. Fonksiyonun n_informative parametresi default olarak 2'dir. Bu parametrenin anlamı biraz karışıktır. Bunun için dokümantasyona başvurabilirsiniz. Ancak n_classes parametresi ile n_informative parametresini aynı değere çekerseniz bir oluşmayacaktı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=4, n_informative=4) 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 (yani kartezyen koordinat sistemi için) nokta üretmektedir. Fonksiyonun birinci parametresi üretilecek noktaların sayısını belirtir. Default durumda fonksiyon iki sınıfa ilişkin eşit sayıda rastgele nokta üretmektedir. 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 parametre (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 birbirinden uzaklaşır. Bu parametrenin default değeri 0.8 biçimindedir. Fonksiyonun noise parametresi çemberlerin düzgünlüğü konusunda etkili olmaktadır. Bu parametre de 0 ile 1 arasında değer alır. Noise değeri Yükseltildikçe gürültü artar yani çemberler çember görünümünden çıkar. Testlerde 0.05 gibi değerleri kullanabilirsiniz. #---------------------------------------------------------------------------------------------------------------------------- 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 hiyeararşik kümeleme işlemini linkage isimli fonksiyon yapar. 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: X1,X2 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: df = pd.read_csv('points.csv') dataset = df.to_numpy(dtype='float32') from scipy.cluster.hierarchy import linkage linkage_data = linkage(dataset) print(linkage_data) Şöyle 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) de 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ştirmeye 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ı verilir. İkinci satırdaki 10 ve 12 değerleri ise 10 numaralı nokta ile 12 numaralı noktanın birleş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 elde edilen yeni küme 13 numaralı küme olacaktır. İşlemler bu biçimde devam ettirilmektedir. Matrisin üçüncü sütunu o satırdaki birleştirmenin alınan ölçüte göre uzaklığını 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: 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') dendrogram fonksiyonu matplotlib kütüphanesi kullanılarak yazılmıştır. Ancak fonksiyon kendi içerisinde show yapmamaktadır. Bu nedenle fonksiyonu çağırdıktan sonra show işlemini yapmalısınız. show işlemi grafiği kapattığı için kütüphaneyi tasarlayanlar show yapmamayı tercih etmiştir. Örneğin: import matplotlib.pyplot as plt linkage_data= linkage(dataset) dendrogram(linkage_data) plt.show() 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 pandas as pd import matplotlib.pyplot as plt df = pd.read_csv('points.csv') dataset = df.to_numpy(dtype='float32') from scipy.cluster.hierarchy import linkage, dendrogram linkage_data= linkage(dataset) plt.title('Points Dendrogram') dendrogram(linkage_data) plt.show() #---------------------------------------------------------------------------------------------------------------------------- K-Means yöntemiyle Agglomerative Hiyerarşik kümeleme yöntemini şö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ı belirtiyor.) Halbuki Agglomerative hiyerarşik kümelemede karmaşıklık O(n ** 3) biçimine kadar yükselmektedir. 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 her zaman Agglomerative kümelemeden daha hızlıdır. - K-Means yöntemi uç dğerlerden (outliers) oldukça etkilenmektedir. Çünkü bir ağırlık merkezi oluşturulurken küme içerisindeki tüm noktalar dikkate alınmaktadır. Halbuki Agglomerative kümeeleme uç değerlerden etkilenmemektedir. - 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. Çünkü noktaların birbirine uzaklığı hiç değişmez. - 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önteminde dendrogram çizilemez. Halbuki Agglomerative yöntemde hangi kümenin hangi kümeyle birleştirildiğ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 metodunun olmadığını anımsayınız. - K-Means yönteminde küme sayısı işin başında sabit bir biçimde belirlenmiş olmak zorundadır. Halbuki 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ı baştan başlatmak gerekir. (Tabii biz AgglomerativeCllustering sınıfında yazlnızca son durumdaki kümelemeyi elde etmekteyiz.) - 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 kümesi demekle bir merkez etrafında serpişmiş veriler anlaşılmaktadır. Eliptik tarzda veriler bu anlamda küresel 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 birbirini kapsayan iki eliptik veri kümesinde K-Means ve Agglomerative yöntemlerin başarıları grafiksel ve sayısal olarak gösterilmeye çalışılmıştır. Ancak bu örnekte kümeleme sonucunda elde edilmiş olan verilerin küme numaraları ile gerçek küme numaraları uyuşmayabilir. Bu durumda elde edilen oransal değerler "bazen başarı durumunu" bazen de "başarısızlık" durumunu beelirtir hale gelecektir. Ancak ne olursa olsun bu oranların %50 civarında olduğu görülmektedir. #---------------------------------------------------------------------------------------------------------------------------- NCLUSTERS = 2 from sklearn.datasets import make_circles dataset, labels = make_circles(100, factor=0.8, noise=0.05) import matplotlib.pyplot as plt plt.title('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(NCLUSTERS, n_init=10) km.fit(dataset) plt.title('K-Means Clustering') 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(NCLUSTERS) ac.fit(dataset) import matplotlib.pyplot as plt plt.title('Agglomerative Clustering') for i in range(NCLUSTERS): plt.scatter(dataset[ac.labels_ == i, 0], dataset[ac.labels_ == i, 1]) plt.show() import numpy as np kmeans_ratio = np.sum(km.labels_ == labels) / len(labels) agglomerative_ratio= np.sum(ac.labels_ == labels) / len(labels) print(f'K-Means ratio: {kmeans_ratio}') print(f'Agglomerative ratio: {agglomerative_ratio}') #---------------------------------------------------------------------------------------------------------------------------- #---------------------------------------------------------------------------------------------------------------------------- 92. Ders - 11/01/2025 - Cumartesi #---------------------------------------------------------------------------------------------------------------------------- #---------------------------------------------------------------------------------------------------------------------------- Kümelemede diğer çok kullanılan yöntem grubundan biri de "yoğunluk tabanlı (density based)" kümele yöntemleridir. Yoğunluk tabanlı 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 küresel alan içerisinde kalan nokta sayısına göre ölçülmektedir. Yöntemde iki parametre başlangıçta uygulamacı tarafından tespit edilir. Bu parametrelere "eps (epsilon)" ve "min_samples" denilmektedir. Eps parametresi küresel bölgenin yarıçapını, min_samples parametresi ise o küresel bölgenin yoğun kabul edilebilmesi için gerekli olan minimum nokta sayısını belirtmektedir. Örneğin eps = 1, min_samples = 10 demek, "eğer 1 yarıçaplı küre içerisinde en az 10 nokta varsa o küresel alan yoğun" demektir. Burada biz "küresel (spherical)" terimini kullandık. Aslında söz konusu uzay iki boyutluysa bir daire, üç boyutluysa bir küre çok boyutluysa o uzayın bir küresini kastetmekteyiz. Üç boyuttan daha fazla boyuta sahip uzaylarda küre (sphere) terimi yerine "hiper küre (hypersphere)" terimi kullanılmaktadır. Yani aslında buradaki küresel kavramının genel terimi hiper küresel (hyperspherical) biçimindedir. İ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 küre denklemi ise şöyledir: (x - a) ** 2 + (y - b) ** 2 + (z - c) ** 2= r ** 2 Yarıçağı r olan ve merkez koordibnatı ci'lerden oluşan N boyutlu uzayın küresinin denklemi de genel olarak şöyle ifade edilebilir: sigma((xi - ci) ** 2) = r ** 2 #---------------------------------------------------------------------------------------------------------------------------- #---------------------------------------------------------------------------------------------------------------------------- Yoğunluk tabanlı algoritmaların en çok kullanılanı DBSCAN (Density Based Spatial Clustering of Applications with Noise) isimli algoritmadır. Algoritma 1996 yılında geliştirilmiştir. 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şilebilen Noktaları (Density Reachable Points): Bir noktanın doğrudan erişilebilen noktalarından biri bir ana nokta 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 "arkadaşımım arkadaşı arkadaşımdır" gibi geçili olarak devam etmektedir. Bu geçişlilik yoğunluk yoluyla erişilebilen noktaların uzayabilmesi anlamına gelir. Burada şu duruma dikkat ediniz: Bir ana noktanın yoğunluk yoluyla erişilebilen noktaları içerisindeki tüm ana noktaların yoğunluk yoluyla erişilebilen noktaları aynıdır. Yani başka bir deyişle bir ana noktanın tüm yoğunluk yoluyla erişilebilen noktalarındaki ana noktaların yoğunluk yoluyla erişilebilen noktaları aynıdır. DBSCAN algoritması aslında bir ana noktanın yoğunluk yoluyla erişilebilen noktalarını bir küme olarak ele alır. Bir Ana Noktanın Sınır Noktaları(Border Points): Bir ana noktanın yoğunluk yoluyla erişilebilen fakat 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ğunluk 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ştirilir. 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 sonucunda K tane küme ve bir de Gürültü Noktaları Kümesi elde edilmiş olur. Algoritmadaki önemli noktalar şunlardır: - Bu algoritmada yoğunluk yoluyla erişilebilen noktalar bir küme olarak elde edilmektedir. - Kümeler arasında yoğun bir bölge oluşturmayan noktalar bulunuyor olabilir. Bu noktalar gürültü noktaları haline gelmektedir. Bu durumu şöyle bir örnekle açıklayabiliriz. UZayda galaksi yoğun yıldızların olduğu bölgelere denilmektedir. İki galaksi arasında yine tek tük yıldızlar olabilir. İşte bu yıldızlar hiçbir galaksiye dahil olmayan gürültü noktalarıdır. - 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. - eps ve min_samples parametresi sabit kalmak üzere algoritmanın her çalıştırılmasından yine aynı kümeler elde edilmeketdir. - Algortimada yine bir uzaklık hesaplama yöntemi (yani metrik) söz konsudur. Yine tipik olarak Öklit uzaklığı kullanılmaktadır. - DBSCAN algoritmasında bir kestirim olanağı yoktur. - DBSCAN algortimasında da bir uzaklık hesabı söz konusu olduğu için farklı skalalara ship veri kümelerinde özellik ölçeklemesi yapılmalıdır. #---------------------------------------------------------------------------------------------------------------------------- #---------------------------------------------------------------------------------------------------------------------------- Aşağıda ChatGPT'nin bedava versiyonu tarafından yazılan bir DBSCAN gerçekleştirimini veriyoruz. Bu gerçekelştirimi incelediğinizde yukarıdaki algoritmanın uygulandığını göreceksiniz. #---------------------------------------------------------------------------------------------------------------------------- import numpy as np from collections import deque def dbscan(data, eps=0.5, min_samples=5): """ DBSCAN kümeleme algoritmasının sıfırdan implementasyonu. Parametreler: data: numpy array, veri seti (n, m) boyutunda, n: örnek sayısı, m: özellik sayısı eps: float, iki nokta arasındaki maksimum mesafe. Kümeleme için eşik değeri. min_samples: int, bir küme için minimum örnek sayısı. Döndürülenler: - labels: numpy array, her veri noktasının küme etiketi. - -1 değeri gürültü noktalarını belirtir. """ # Veri setinin boyutları n_points = data.shape[0] # Etiketler, -1 gürültü anlamına gelir. labels = -np.ones(n_points) # Ziyaret edilen noktaları takip etmek için bir liste visited = np.zeros(n_points, dtype=bool) # Komşuları hesaplamak için bir yardımcı fonksiyon def region_query(point_idx): """ Belirli bir noktaya yakın olan noktaların indekslerini döndürür. """ neighbors = [] for i in range(n_points): if np.linalg.norm(data[point_idx] - data[i]) <= eps: neighbors.append(i) return neighbors # Küme numarasını izlemek için bir sayaç cluster_id = 0 # DBSCAN algoritması for i in range(n_points): if visited[i]: continue # Noktayı ziyaret et visited[i] = True # Komşuları bul neighbors = region_query(i) # Yeterli komşuya sahip değilse, bu nokta gürültü olarak işaretlenir if len(neighbors) < min_samples: labels[i] = -1 else: # Yeni bir küme başlat labels[i] = cluster_id cluster_id += 1 # Komşuları işle queue = deque(neighbors) while queue: current_point = queue.popleft() if not visited[current_point]: visited[current_point] = True current_neighbors = region_query(current_point) # Eğer yeterli komşu varsa, komşuları kuyruğa ekle if len(current_neighbors) >= min_samples: queue.extend(current_neighbors) # Komşu zaten başka bir kümeye ait değilse, onu mevcut kümeye dahil et if labels[current_point] == -1: labels[current_point] = labels[i] return labels #---------------------------------------------------------------------------------------------------------------------------- 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)[source] Metodun ilk parametresi yarıçap belirten eps parametresidir. Bu parametrenin default değerinin 0.5 olduğunu görüyorsunuz. Özellik ölçeklemesinden sonra bu 0.5 değeri denemerl için uygun bir değerdir. İ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 belirtir. Diğer parametreler için dokümanlara bakabilirsiniz. DBSCAN sınıfı türünden nesne yaratıldıktan sonra yine klasik sklearn işlemleri yapılmaktadır. Kümeleme sınıfın fit metodu ile gerçekleştirilir. fit işlemi sonrasında nesnenin özniteliklerinden kümeleme bilgileri alınabilir . 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 veri kğmesindeki indeks numalaralarını vermektedir. components_: Ana noktaların hepsinin bulunduğu NumPy dizisini vermektedir. n_features_in_: Veri kümesindeki sütun sayısını vermektedir. DBSCAN algoritmasında uygulamacının eps 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 şeyler söylemek o kadar kolay değildir. Ancak min_samples değerinin 1 olmaması gerekir. 2 olması da uygun değildir. Buradaki değer için alt limit özellik sayısından bir fazla biçimde seçilebilir. Ö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. Örneğin 10 özelliğin (sütunun) söz konusu olduğu bir veri kümesinde min_samples değeri de 11 olabilir. Ancak yüksek min_samples seçimi daha fazla küme oluşmasına yol açacaktır. DBSCAN sınıfındaki default min_samples değerinin 5 olduğuna dikkat ediniz. 5 değeri bir başlangıç için kullanılabilir. Deneme yanılma yöntemiyle bu değer artırılıp azaltılabilir. Epsilon değerinin belirlenmesi için "en yakın komuşuğa (nearest neighbours)" yönelik yöntemler önerilmiştir. Ancak bu değerin deneme yanılma yöntemiyle belirlenmesi daha iyi bir sonucun elde edilmesini sağlayacaktır. O halde uygulamacı önce min_samples parametresini belirleyip daha sonra eps parametresiyle oynayarak nihai ayarlamayı yapabilir. Aşağıda örnekte daha önce kullanmış olduğumuz "points.csv" verileri üzerinde DBSCAN algoritmasını uyguluyoruz. Noktalar şunlardı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 Biz buradaki noktalar için eps=1.5 ve min_samples=3 parametrelerini kullandık. Bu değerler için algoritma iki küme oluşturmuştur. Örneğimizde çizdiğimiz grafikte ayrıca gürültü noktalarını da gösterdik. Siz de örneğimizdek, eps ve min_samples değerlerini değiştirerek elde edilen grafiği gözlemleyebilirsiniz. Örneğin eps=1, min_samples=2 değerleri için dört küme oluşturulmaktadır. #---------------------------------------------------------------------------------------------------------------------------- import numpy as np import pandas as pd import matplotlib.pyplot as plt df = pd.read_csv('points.csv') dataset = df.to_numpy(dtype='float32') from sklearn.cluster import DBSCAN dbs = DBSCAN(eps=1.5, min_samples=3) dbs.fit(dataset) nclusters = np.max(dbs.labels_) + 1; if nclusters == -1: nclusters = 0 plt.title('DBSCAN Clustered Points', fontsize=12) 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. Biz DBSCAN nesnesini eps = 0.5, min_samples = 5 parametresiyle (yani default değerle çalıştırdığımızda) iki küme oluşmaktadır. Parametrelerde oynaamalar yaparak değişik küme sayılarını elde edebiriz. Örneğin eps = 0.45 ve min_samples = 5 parametreleri için üç kğme oluşmaktadır. Biz aşağıdaki örnekte default değerleri kullandık. Ancak siz bu parametreler üzerinde oynamalar yaparak oluşturulan kümeleri gözlemleyebilirsiniz. #---------------------------------------------------------------------------------------------------------------------------- NCLUSTERS = 3 import pandas as pd df = pd.read_csv('iris.csv') dataset = df[['SepalLengthCm','SepalWidthCm','PetalLengthCm','PetalWidthCm']].to_numpy('float32') from sklearn.preprocessing import StandardScaler ss = StandardScaler() ss.fit(dataset) transformed_dataset = ss.transform(dataset) from sklearn.cluster import DBSCAN dbs = DBSCAN() dbs.fit(transformed_dataset) import numpy as np nclusters = np.max(dbs.labels_) + 1 from sklearn.decomposition import PCA pca = PCA(n_components=2) reduced_dataset = pca.fit_transform(dataset) import matplotlib.pyplot as plt plt.figure(figsize=(10, 8)) plt.title('Clustered Points') plt.title('DBSCAN Clustered Points', fontsize=12) for i in range(nclusters): plt.scatter(reduced_dataset[dbs.labels_ == i, 0], reduced_dataset[dbs.labels_ == i, 1]) plt.scatter(reduced_dataset[dbs.labels_ == -1, 0], reduced_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 (yani küresel olmayan) veri kümelerinde (örneğin iç içe geçmiş elips'ler gibi noktalara sahip) daha önce K-Means ve Agglomerative hiyerarşik kümeleme yöntemlerinin iyi çalışmdığını görmüştük. İşte bu tarzdaki veri kümelerinde yoğunluk tabanlı yöntemler iç ve dış elips verilerini iyi bir biçimde kümeleyebilmektedir. Aşağıdaki örnekte iç içe iki eliptik veri kümesi oluşturulup DBSCAN yöntemiyle bunlar kümelendirilmiştir. Bu örnekte biz min_samples değerini default değer olan 5'te tuttuk ve eps 0.35 olarak aldık. #---------------------------------------------------------------------------------------------------------------------------- from sklearn.datasets import make_circles dataset, labels = make_circles(100, factor=0.4, noise=0.06) 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 DBSCAN dbs = DBSCAN(eps=0.35) dbs.fit(dataset) import numpy as np nclusters = np.max(dbs.labels_) + 1 plt.figure(figsize=(10, 8)) plt.title('DBSCAN Clustered Points', fontsize=12) 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() #---------------------------------------------------------------------------------------------------------------------------- 93. Ders - 12/01/2024 - Pazar #---------------------------------------------------------------------------------------------------------------------------- #---------------------------------------------------------------------------------------------------------------------------- Diğer bir yoğunluk tabanlı kümeleme algoritması da OPTICS (Ordering Points To Identify Clustering Structure) 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 dışında değişik amaçlarla da kullanılabilmektedir. OPTICS algoritmasında iki temel uzaklık kavramı vardır: "Ana uzaklık (core distance)" ve "erişilebilir uzaklık (reachability distance)". Algoritma için öncelikle bu uzaklıkların ne anlama geldiğinin anlaşılması gerekir. Algoritmada yineyarıçap belirten eps ve en az nokta sayısını belirten min_pts değerlerinin girdi olarak verildiğini düşünelim. Ana uzaklık (core distance) bir ana nokta için söz konusu olan bir uzaklıktır. Yani eğer bir noktanın eps yarıçapında min_pts kadar nokta varsa bu noktanın bir ana uzaklığı vardır. Ana uzaklık tam olarak min_pts kadar noktayı içine alan uzaklıktır. Örneğin bir ana noktanın eps uzaklığında 10 tane nokta olsun. Ancak min_pts değerinin 3 olduğunu düşünelim. Bu duurmda ana uzaklık bu ana noktanın tam olarak 3 tane noktayı içine alacak yarıçapının uzunluğudur. Yani başka bir deyişle biz bu ana noktanın eps komşuluğunda olan 10 tane noktayı ele alıp bunların bu ana noktadan uzaklıklarını küçükten büyüğe sıraya dizersek 3'üncü sıradaki uzaklık bu ana noktanın ana uzaklığı olacaktır. Eps değerinin çok büyük seçildiğini düşünelim. Bu durumda tüm noktalar eps komşuluğunda kalacaktır. İşte bu noktaya en yakın min_pts'inci nokta o ana noktanın ana uzaklığıdır. OPTICS algoritmasında tüm ana noktalar için bir ana uzaklık hesaplanabilmektedir. Tabii ana nokta olmayan noktaların ana uzaklığı söz konusu değildir. Eps değerinin çok büyük seçildiği durumda tüm noktaların ana nokta haline geleceğine dikkat ediniz. Bir ana noktanın eps komşuluğundaki tüm noktalarının bir erişilebilir uzaklığı vardır. Ana nokta cp olmak üzere bu ana noktanın eps komuşupundaki nokta da pp olmak üzere bu pp noktasının erişilebilir uzaklığı şöyle hesaplanmaktadır: max(cp'nin_ana_uzaklığı, cp_ile_pp'nin_uzaklığı) Böylece bir ana noktanın eps komşuluğundaki bir naktasının erişilebilir uzaklığı için şu durum söz konusudur: - Eğer ana noktadan bu noktaya uzaklık ana noktanın ana uzaklığından düşükse bu noktanın erişebilir uzaklığı ana uzaklık olacaktır. - Eğer ana noktadan bu noktaya uzaklık ana noktanın erişilebilir uzaklığından yüksekse bu noktanın erişilebilir uzaklığı ana noktanın bu noktaya uzaklığı olacaktır. Burada bir noktaya dikkatiniz çekmek istiyoruz. Bir nokta birden fazla ana noktanın eps komşuluğunda bukunuyor olabilir. Bu durumda bu noktanın erişilebilir uzaklığı en küçük erişilebilir uzaklığı olarak ele alınmaktadır. OPTICS algoritmasında yukarıdaki işlemler uygulanıp verilen eps ve min_pts için her noktaya ilişkin bir erişilebilir uzaklık elde edilmektedir. Bu erişilebilirlik uzaklıklarının oluşturduğu grafiğe "erişilebilirlik grafiği" denilmektedir. Algoritmanın amacı her noktanın erişilebilirlik uzaklığını tespit etmektir. Kümeleme işlemi noktaların elde edilmiş olan erişilebilirlik uzaklıkları göz önüne alınarak birkaç yöntemle yapılmaktadır. OPTICS algoritmasında eps değerinin çok büyük olduğunu varsayalım. Bu durumda ne olur? İşte bu durumda her nokta bir ana nokta durumuna gelir. Bu duurmda her noktanın bir erişilebilirlik uzaklığı söz konusu olacaktır. OPTICS algoritmasında bir nokta hiçbir ana noktanın eps komşuluğunda değilse bu nokta yine gürültü noktası olarak tespit edilecektir. Eğer eps değeri çok yüksek tutulursa bu durumda erişilebilirlik uzunluklarına bakılarak da gürültü noktaları tespit edilebilir. Her noktanın erişilebilirlik uzaklığı belirlendikten sonra kğmelemenin nasıl yapılacağına yönelik çeşitli yöntemler bulunmaktadır. Örneğin erişilebilirlik uzaklıkları sıraya dizilebilir. Bu sıralamada yüksek atlamaların olduğu yerler küme geçişleri olarak belirlenebilir. Biz burada bu ayrıntılara girmeyeceğiz. Aşağıda ChatGPT tarafından yazılan OPTICS algoritması verilmiştir. Buradaki gerçekleştirim size bir fikir verebilir. Ancak bu gerçekleştirim performans açısından sorunludur. #---------------------------------------------------------------------------------------------------------------------------- import numpy as np import math # 1. Adım: Mesafe Hesaplama (Euclidean Distance) def euclidean_distance(p1, p2): return np.sqrt(np.sum((p1 - p2) ** 2)) # 2. Adım: Core Distance Hesaplama def core_distance(p, points, min_pts, epsilon): # Diğer noktalarla olan mesafeleri hesapla distances = [euclidean_distance(p, other) for other in points] # `epsilon` mesafesi içinde kalan komşuları al neighbors = [dist for dist in distances if dist <= epsilon] # Eğer komşu sayısı yeterliyse, çekirdek mesafesini döndür if len(neighbors) >= min_pts: return sorted(neighbors)[min_pts-1] # En uzak `MinPts`-1'ci komşunun mesafesini döndür else: return None # Yeterli komşu yoksa çekirdek mesafesi yoktur (gürültü) # 3. Adım: Reachability Distance Hesaplama def reachability_distance(p, q, core_distances, epsilon): distance = euclidean_distance(p, q) if core_distances[p] is None: # P bir çekirdek noktası değilse return None return max(core_distances[p], distance) # 4. Adım: OPTICS Algoritmasının Çalıştırılması def optics(points, epsilon, min_pts): n = len(points) # Başlangıçta tüm noktalar "işlenmemiş" olarak işaretlenir visited = [False] * n processed = [False] * n # İşlem tamamlanmamış noktalar reachability_distances = [None] * n # Her nokta için erişilebilirlik mesafeleri core_distances = [None] * n # Çekirdek mesafeleri ordering = [] # Sıralama (OPTICS sıralaması) # Her noktayı sırasıyla işleyelim for i in range(n): if not visited[i]: visited[i] = True # Core distance hesapla core_distances[i] = core_distance(points[i], points, min_pts, epsilon) # Eğer çekirdek mesafesi yoksa, o zaman bu nokta gürültü noktasıdır if core_distances[i] is not None: # Sıralamayı başlat ordering.append(i) expand_cluster(i, points, epsilon, min_pts, core_distances, reachability_distances, processed, ordering) return ordering, reachability_distances, core_distances # 5. Adım: Kümeleme ve Gürültülerin Ayırt Edilmesi def expand_cluster(p_index, points, epsilon, min_pts, core_distances, reachability_distances, processed, ordering): # Komşu noktaları topla neighbors = [] for i in range(len(points)): if i != p_index and euclidean_distance(points[p_index], points[i]) <= epsilon: neighbors.append(i) for q_index in neighbors: if not processed[q_index]: # Eğer q işlenmemişse processed[q_index] = True core_distances[q_index] = core_distance(points[q_index], points, min_pts, epsilon) # Reachability mesafesini güncelle new_reachability_distance = reachability_distance(p_index, q_index, core_distances, epsilon) if reachability_distances[q_index] is None or new_reachability_distance < reachability_distances[q_index]: reachability_distances[q_index] = new_reachability_distance ordering.append(q_index) # Örnek Veri Kümesi points = np.array([ [1, 1], [1, 2], [2, 2], [8, 8], [8, 9], [25, 25] ]) # Parametreler epsilon = 2 # Mesafe eşik değeri min_pts = 2 # MinPts # Algoritmayı çalıştır ordering, reachability_distances, core_distances = optics(points, epsilon, min_pts) # Sonuçları yazdır print("Ordering (OPTICS sıralaması):", ordering) print("Reachability Mesafeleri:", reachability_distances) print("Core Mesafeleri:", core_distances) #---------------------------------------------------------------------------------------------------------------------------- OPTICS algoritması scikit-learn kütüphanesindeki sklearn.cluster modülünde bulunan OPTICS isimli sınıfla 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) Mettotaki cluster_method parametresi "xi" biçimde ya da "dbscan" biçiminde girilebilir. Bu parametrenin default durumunun "xi" biçiminde olduğunu görüyorsunuz. Bu durumda algortima yukarıda açıkladığımız biçimde yürütülür. Yani noktaların erişilebilen uzaklıklarına dayalı olarak kümeleme yapmaktadır. "xi" yönteminde xi parametresi de etkili olmaktadır. Bu parametre kümeleri tespit edebilmek için sıraya dizilmiş erişilebilir uzaklıklardaki farklılaşmanın eşik değerini belirtmek için kullanılmaktadır. Eğer cluster_method parametresi "dbscan" olarak girilirse bu durumda DBSCAN algoritması uygulanır. Yani bu durumda uygulanan algoritmanın DBSCAN algoritmasından bir farkı kalmaz. Ancak ek olarak bize eriişilebilen uzaklıklar da verilir. Eğer cluster_method parametresi "dbscan" olarak girilirse bu durumda DBSCAN algoritması kullanılacağı için bizim eps parametresini de girmemiz gerekir. Aksi takdirde sanki eps=0 gibi tüm noktalar gürültü noktası biçiminde oluşacaktır. OPTICS türünden nesne yaratıldıktan sonra eğitim fit metoduyla yapılmaktadır. Diğer kğmeleme sınıflarfında olduğu gibi kümeleme bilgisi yine nesnenin labels_ özniteliğinden elde edilmektedir. Nesnenin ordering_ özniteliği noktaların hangi sırada ele alındığına ilişkin bilgi vermektedir. Nesnenin reachability_ özniteliği noktaların erişilebilir uzaklıklarını, core_distances_ özniteliği ise noktaların ana uzaklıklarını bize vermeketdir. Nesnenin cluster_hierarchy_ özniteliği erişilebilir uzaklıklardan hareketle bir dendgrogram çizilmesini sağlamak için bir bağlantı matrisi vermektedir. (Ancak burada verilen bağlantı matrisi SciPy'da kullanmış olduumuz dendrgram fonksiyonunun kullandığı formata uygun değildir. 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 import pandas as pd import matplotlib.pyplot as plt df = pd.read_csv('points.csv') dataset = df.to_numpy(dtype='float32') from sklearn.cluster import OPTICS opt = OPTICS(min_samples=3, eps=1, cluster_method='xi') opt.fit(dataset) print(opt.labels_) print(opt.reachability_) print(opt.ordering_) print(opt.core_distances_) print(opt.cluster_hierarchy_) nclusters = np.max(opt.labels_) + 1; if nclusters == -1: nclusters = 0 plt.title('OPTICS Clustered Points', fontsize=12) for i in range(nclusters): plt.scatter(dataset[opt.labels_ == i, 0], dataset[opt.labels_ == i, 1]) plt.scatter(dataset[opt.labels_ == -1, 0], dataset[opt.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() plt.show() #---------------------------------------------------------------------------------------------------------------------------- Aşağıdaki örnekte min_samples=9, xi=0.1 için zambak veri kümesi OPTICS algoritmasına göre kümelendirilmiştir. Örnekte bu parametrelerle 3 küme oluşturulmuştur. #---------------------------------------------------------------------------------------------------------------------------- import numpy as np import pandas as pd import matplotlib.pyplot as plt df = pd.read_csv('points.csv') dataset = df.to_numpy(dtype='float32') from sklearn.cluster import OPTICS opt = OPTICS(min_samples=3, eps=1, cluster_method='xi') opt.fit(dataset) print(opt.labels_) print(opt.reachability_) print(opt.ordering_) print(opt.core_distances_) print(opt.cluster_hierarchy_) nclusters = np.max(opt.labels_) + 1; if nclusters == -1: nclusters = 0 plt.title('OPTICS Clustered Points', fontsize=12) for i in range(nclusters): plt.scatter(dataset[opt.labels_ == i, 0], dataset[opt.labels_ == i, 1]) plt.scatter(dataset[opt.labels_ == -1, 0], dataset[opt.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ğıda iç içe eliptik veri kümesine OPTICS algoritması uygulanmıştır. Burada min_samples parametresi uygun bir biçimde ayaralanarak iç ve dış eliptik noktalar birbirinden ayrılmıştır. #---------------------------------------------------------------------------------------------------------------------------- from sklearn.datasets import make_circles dataset, labels = make_circles(100, factor=0.2, 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=10) 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 yavaş çalışma eğilimindedir. Çünkü eps değeri büyük tutulduğunda tüm noktalar arasında uzaklık hesabı yapılmak zorunda kalnır. - OPTICS yöntemi veri kğmesinde farklı yoğunluklu kümeler buulunduğu durumda daha iyi performans gösterebilir. DBSCAN yönteminde farklı yoğunluklu bölgeler eps sınırları dışında kalabilir. Halbuki OPTICS yönteminde erişim uzaklıkları dikkate alındığı için farklı yoğunluklu bölgeler tespit edilebilecektir. - OPTICS algoritmasında biz yalnızca min_samples parametresini ve xi değerini belirleriz. Halbuki DBSCAN algoritmasında biz epsilon değerini de belirlemek zorundayız. - DBSCAN algortiması daha esnektir. DBSCAN'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ı küresel olmayan (eliptik) verilerde K-Means ve Agglomerative hiyerarşik yönteme göre daha iyi sonuç vermektedir. - OPTICS algoritmasındaki erişilebilir uzaklıklar hesaplandığı için bu uzaklık bilgilerinden başka amaçlarla da faydalanılabilmektedir. #---------------------------------------------------------------------------------------------------------------------------- #---------------------------------------------------------------------------------------------------------------------------- Makine öğrenmesinde kullanılan naif yöntemlerden biri de "k-NN (k-Nearest Neighbors)" denilen yöntemdir. Bu yönteme Türkçe "En Yakın k Komşuluk Yöntemi" de diyeceğiz. k-NN temelde denetimli (supervised) bir yönteme benzemektedir. Ancak yöntemin denetimsiz (unsupervised) modellerde uygulama alanları da vardır. En yakın k komşuluk yöntemi fikir olarak makine öğrenmesinin başka alanlarında çeşitli süreçlerde de kullanılmaktadır. k-NN yönteminin dayandığı fikir oldukça basittir. Yöntemdeki k harfi en yakın kaç komşuya bakılacağına ilişkin değeri temsil eder. Bu k değeri yöntemin bir hyper parametresidir. Örneğin bu yöntemde k'nın 3 olması "en yakın 3 komşuya başvur" anlamına gelmektedir. k-NN yöntemi özellikle sınıflandırma problemlerinde kullanım alanı bulmaktadır. Ancak regresyon problemlerinde de duruma göre kullanılabilmektedir. Yöntemde eğitim veri kümesi ile kestirilecek değerler aynı anda işleme sokulmaktadır. Yani bu yöntemde önce eğitim yapılıp oradan bilgiler elde edilip kestirim sırasında o bilgilerden faydalanılmamaktadır. Eğitim verileriyle kestirim verileri aynı süreçte işleme sokulmaktadır. En yakın k komşuluk yönteminin dayandığı basit temel şudur: Bir kestirim yapılacaksa kestirim yapılacak noktaya en yakın k tane noktanın durumuna bakılır. Sınıflandırma işleminde kesitirilecek noktaya en yakın k tane noktada hangi sınıfsal değerler (etiketler) daha fazla ise noktanın o sınıfa ilişkin olduğu kabul edilir. Yöntemi şu sürece de benzetilebiliriz: Birisinin belli bir özelliği hakkında bilgi elde etmek isteyelim. Bunun için onun en yakın k tane arkadaşını inceleyip o k tane arkadaşın ilgili özelliğine bakarak yargıda bulunabiliriz. Tabii buradaki yargı oldukça naif bir temele dayanmaktadır. Bu naif yargı "bana arkadaşını söyle senin kim olduğunu söyleyeyim" özdeyişini anımsatmaktadır. #---------------------------------------------------------------------------------------------------------------------------- #---------------------------------------------------------------------------------------------------------------------------- 94. Ders - 18/01/2024 - Cumartesi #---------------------------------------------------------------------------------------------------------------------------- #---------------------------------------------------------------------------------------------------------------------------- Aşağıda k-NN yöntemine göre sınıflandırma yapan düz mantıkla yazılmış bir örnek verilmişri. (Bu örnek ChatGPT tarafından yazılmıştır.) import numpy as np from collections import Counter def euclidean_distance(x1, x2): return np.sqrt(np.sum((x1 - x2) ** 2)) class KNN: def __init__(self, k=3): self.k = k def fit(self, X_train, y_train): self.X_train = X_train self.y_train = y_train def predict(self, X_test): predictions = [self._predict(x) for x in X_test] return np.array(predictions) def _predict(self, x): distances = [euclidean_distance(x, x_train) for x_train in self.X_train] k_indices = np.argsort(distances)[:self.k] k_nearest_labels = [self.y_train[i] for i in k_indices] most_common = Counter(k_nearest_labels).most_common(1) return most_common[0][0] if __name__ == "__main__": X_train = np.array([[1, 2], [2, 3], [3, 3], [6, 5], [7, 8], [8, 8]]) y_train = np.array([0, 0, 0, 1, 1, 1]) # 0 ve 1 sınıfları # Test verisi X_test = np.array([[5, 5], [3, 4]]) model = KNN(k=3) model.fit(X_train, y_train) predictions = model.predict(X_test) print("Tahminler:", predictions) Burada düz mantıkla kestirilecek noktaların tüm noktalara uzaklıkları bir liste içlemiyle elde edilmiştir. Sonra da Python'un standart Counter sınıfıyla en çok yinelenen etiket değerleri bulunmuştur. Tabii buradaki algoritma düz mantık (brute force) biçimde oluşturulmuştur. Algoritmayı hızlandırmak için fit işlemi sırasında ağaç yapıları oluşturulup bunlardan faydalanılabilmektedir. Biz bu algoritmaları burada ele almayacağız. #---------------------------------------------------------------------------------------------------------------------------- #---------------------------------------------------------------------------------------------------------------------------- k-NN yönteminde k değeri bir hyper parametredir. Yani bunun uygulamacı tarafından algortimaya verilmesi gerekir. Pekiyi bu k değerini uygulamacı nasıl tespit etmelidir? Aslında bu konuda kesin bir yöntem önermek mümkün değildir. En çok uygulanan yöntem veri kümesini değişik k değerleri için sınıflandırmak ve en iyi k değerini deneme yanılma yoluyla tespit etmektir. Uygulamacıların bazıları k değerini görsel ya da nümerik olarak bir çeşit dirsek grafiği eşliğinde belirlemeye çalışmaktadır. Eğer k değeri çok büyük seçilirse yakınlığın bir anlamı kalmaz. Ayrıca işlemler de zaman bakımından uzar. k değeri çok küçük seçilirse genelleme yeteneği azalır. O halde k değerinin makul bir biçimde seçilmesi gerekir. Veri kümesi çok küçükse k değerinin azaltılması, çok büyükse daha yüksek tutulması uygun olabilir. 5 gibi bir değer çoğu veri kümesi için ortalama makul bir değerdir. k-NN yöntemi kullanılmadan önce özellik ölçeklemesi yapılmalıdır. Çünkü uzaklık hesabında belli bir sütunun diğerinden daha etikili olması genellikle istenmez. #---------------------------------------------------------------------------------------------------------------------------- #---------------------------------------------------------------------------------------------------------------------------- k-NN yöntemi scikit-learn kütüphanesinde sklearn.neighbors modülünde bulunan çeşitli sınıflar yoluyla gerçekleştirilmiştir. Biz burada bu kütüphanedeki bazı sınıfların kullanımları üzerinde duracağız. KNeighborsClassifier sınıfı k-NN yöntemiyle sınıflandırma yapmak için kullanılmaktadır. Sınıfın __init__ metodunun parametrik yapısı şöyledir: class sklearn.neighbors.KNeighborsClassifier(n_neighbors=5, *, weights='uniform', algorithm='auto', leaf_size=30, p=2, metric='minkowski', metric_params=None, n_jobs=None) Burada n_neighbors parametresi kaç komşuluğa bakılacağını belirtmektedir. Yani bu parametre k değerini belirtmektedir. algorithm parametresi en yakın komşuluk bulmada kullanılacak algoritmayı belirtmektedir. Algoritma için girilecek iki tipik değer "ball_tree" ve "kd_tree" biçimindedir. Bu parametrenin default değerinin "auto" olduğuna dikkat ediniz. Bu durumda veri kümesine en uygun algoritma seçilmektedir. metric parametresi uzaklık ölçmek için kullanılmaktadır. Bu parametrenin default değerinin "minkowski" biçiminde girildiğine dikkat ediniz. Minkowski uzaklığı Öklit uzaklığının genel bir biçimidir. Minkowski uzaklığındaki üs belirten p değeri de p parametresiyle belirlenebilmektedir. Bu p değerinin default durumda 2 olduğunu görüyorsunuz. Bu durumda default uzaklık olarak Öklit uzaklığı kullanılacaktır. Örneğin: from sklearn.neighbors import KNeighborsClassifier knc = KNeighborsClassifier(5) KNeighborsClassifier nesnesi yaratıldıktan sonra fit işlemi yapılır. fit işlemi sırasında en yakın komuşukların hızlı bulunması için bazı hazırlıklar yapılmaktadır. Örneğin: knc.fit(dataset_x, dataset_y) Bundan sonra artık kestirim yapılabilir. Kestirim için yine sınıfın predict metodu kullanılmaktadır. Örneğin: predict_result = knc.predict(predict_dataset_x) Sınıfın kneighbors isimli metodu verilen noktalara en yakın k tane noktaya olan uzaklıkları ve bu nktaların indekslerini bize ikili bir demet olarak vermektedir. Aşağıda zambak (iris) veri kümesi üzerinde bir uygulama verişmiştir. #---------------------------------------------------------------------------------------------------------------------------- import pandas as pd df = pd.read_csv('iris.csv') dataset_x = df[['SepalLengthCm','SepalWidthCm','PetalLengthCm','PetalWidthCm']].to_numpy('float32') dataset_y = df['Species'].to_numpy(dtype='str') from sklearn.preprocessing import StandardScaler ss = StandardScaler() ss.fit(dataset_x) scaled_dataset_x = ss.transform(dataset_x) from sklearn.neighbors import KNeighborsClassifier knc = KNeighborsClassifier(5) knc.fit(scaled_dataset_x, dataset_y) predict_df = pd.read_csv('predict-iris.csv') predict_dataset = predict_df.to_numpy(dtype='float32') scaled_dataset_predict = ss.transform(predict_dataset) predict_result = knc.predict(scaled_dataset_predict) print(predict_result) #---------------------------------------------------------------------------------------------------------------------------- k-NN yöntemiyle regresyon problemlerinin çözümünde tipik olarak noktaya en yakın k tane noktanın değerlerinin ortalaması heaplanmaktadır. Bunun için scikit-learn içerisindeki KNeighborsRegressor sınıfı kullanılmaktadır. Sınıfın __init__ metodunun parametrik yapısı şöyledir: class sklearn.neighbors.KNeighborsRegressor(n_neighbors=5, *, weights='uniform', algorithm='auto', leaf_size=30, p=2, metric='minkowski', metric_params=None, n_jobs=None) Yine metodun n_neighbors parametresi k değerini belirtmektedir. Bu değerin default olarak 5 alındığını görüyorsunuz. Diğer parametreler yine aynıdır. Sınıf benzer biçimde kullanılmaktadır. Örneğin: knr = KNeighborsRegressor(5) nr.fit(scaled_dataset_x, dataset_x) Kestirim yine sınıfın predic metoduyla yapılmaktadır. predict_result = knr.predict(predict_dataset_x) Aşağıda "Boston Hosing Prices" veri kümesi üzerinde k-NN ile regresyon işleminebir örnek verilmiştir. #---------------------------------------------------------------------------------------------------------------------------- import pandas as pd df = pd.read_csv('housing.csv', delimiter=r'\s+', header=None) dataset_y = df.iloc[:, -1].to_numpy() df.drop([8, 13], axis=1, inplace=True) dataset_x = df.to_numpy('float32') from sklearn.preprocessing import StandardScaler ss = StandardScaler() ss.fit(dataset_x) scaled_dataset_x = ss.transform(dataset_x) from sklearn.neighbors import KNeighborsRegressor knr = KNeighborsRegressor(5) knr.fit(scaled_dataset_x, dataset_x) predict_df = pd.read_csv('predict-boston-housing-prices.csv', delimiter=r'\s+', header=None) predict_df.drop([8], axis=1, inplace=True) predict_dataset_x = predict_df.to_numpy('float32') scaled_predict_dataset_x = ss.transform(predict_dataset_x) predict_result = knr.predict(scaled_predict_dataset_x) print(predict_result) #---------------------------------------------------------------------------------------------------------------------------- sklearn.neigbours modülünde başka yararlı sınıflar da vardır. NearestNeighbors sınıfı sınıflandırma ya da regresyon işlemini yapmaz. Denetimsiz biçimde yalnızca verilen noktalara ilişkin en yakın k komşuları tespit eder. Aynı zamanda bu noktaların en yakın komşulara uazaklıklarını da vermektedir. sklearn.neigbours modülündeki diğer bir faydalı sınıf da NearestCentroid isimli sınıftır. Bu sınıf denetimli (supervised) biçimde çalışmaktadır. Biz bu sınıfın fit metodunu dataset_x ve dataset_y değerleriyle çağırırız. Metot her sınıf için ağırlık merkezini (yani orta noktasını) tespit eder. Sonra predict işlemini yaptığımızda metot verilen noktalar hangi ağırlık merkezine yakınsa noktaların o sınıflara ilişkin olduğu sonucunu çıkartır. Bu sınıfı KNeighborsClassifier sınıfı ile karıştırmayınız. KNeighborsClassifier sınıfı en yakın k komşuluğun y değerlerini oylayarak tespiti yapmaktadır. Modüldeki diğer sınıflar nispeten daha az kullanılmaktadır. Bunları dokümanlardan inceleyebilirsiniz. #---------------------------------------------------------------------------------------------------------------------------- #---------------------------------------------------------------------------------------------------------------------------- Bu bölümde istatistike ve veri biliminde çok kullanılan "kovaryans" ve "korelasyon" kavramlarını ele alacağız. #---------------------------------------------------------------------------------------------------------------------------- #---------------------------------------------------------------------------------------------------------------------------- Biz giriş derslerinde "varyans" kavramını görmüştük. Varyans standart sapmanın karesine denilmektedir. İstatistikte bazı konularda varyans kavramı çok kullanılırken bazı konularda standart sapma kavramı çok kullanılmaktadır. Dolayısıyla bu iki kavram birbirleriyle ilişkili olduğu halde bunlar için iki farklı terim ismi uydurulmuştur. Giriş konularında da gördüğümüz gibi varyans işlemi NumPy ktüphanesinde 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. Birbirinden 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 y olsun. 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 kovaryansları 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 iki değişken arasında ilişkiyi belirtmektedir. İki değişkendne çok değişkenlerin kovaryasnları ancak birbirlerine göre elde edilebilir. Bu durumda bir kovaryans matrisi oluşacaktır. İki değişken arasındaki kovaryans hesabı şöyle hesaplanmaktadır: Kovaryans(x, y) = sigma((xi - xbar) * (yi - ybar)) / n Örneğin değerler aşağıdaki gibi olusn: X Y --- --- 1 5 2 4 3 3 4 2 5 1 Burada X ve Y ortalaması şöyledir: Xbar = 3 Ybar = 3 Kovaryans da şöyle hesaplanır: +-----+--------+--------+---------------------+---------------------+---------------------------------------------+ | i | x_i | y_i | x_i - x̄ | y_i - ȳ | (x_i - x̄) * (y_i - ȳ) | +-----+--------+--------+---------------------+---------------------+---------------------------------------------+ | 1 | 1 | 5 | -2 | 2 | -4 | | 2 | 2 | 4 | -1 | 1 | -1 | | 3 | 3 | 3 | 0 | 0 | 0 | | 4 | 4 | 2 | 1 | -1 | -1 | | 5 | 5 | 1 | 2 | -2 | -4 | +-----+--------+--------+---------------------+---------------------+---------------------------------------------+ Buradan kovaryasn şöyle hesaplanır: Kovaryans(X, Y) = -10 / 5 = -2 Bunu bir fonksiyon olarak da şöyle yazabiliriz: import numpy as np def cov(x, y): return np.sum((x - np.mean(x)) * (y - np.mean(y))) / len(x) Aynı değişkenin aynı değişkenle kovaryansının zaten varyans anlamına geldiğine dikkat ediniz. Yani cov(x, x) aslında var(x) ile aynı anlamdadır. İki değişken arasında elde edilen kovaryans başka iki değişken arasında elde edilen kovaryans ile kıyaslanamaz. Bunların arasında bir kıyaslama yapılabilmesi için özellik ölçeklemesi uygulanmalıdır. Zaten özellik ölçeklemesi uygulanmış olan kovaryansa da korelasyon denilmektedir. 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. Kovaryans matrisinde köşegenler değişkenlerin varyanslarını belirtir. Çünkü cov(x, x) zaten var(x) anlamındadır. Örneğin: x = np.array([1, 2, 3, 4, 5]) y = np.array([3, 6, 8, 10, 12]) result = np.cov(x, y, ddof=0) print(result) Buradan şöyle bir sonuç elde edilmiştir: [[2. 4.4 ] [4.4 9.76]] Örneğimizde iki değişken bulunduğu için kovaryans matrisi de 2x2 boyuttadır. Bu matrisin köşegenleri varyasn değerlerini vermektedir. İki değiikenden daha fazla değişkenler için kovaryans elde etmek için değerler bir matriste toplanıp bu matris birinci parametreye girilebilir. Örneğin: x = np.array([1, 2, 3, 4, 5]) y = np.array([3, 6, 8, 10, 12]) z = np.array([3, 5, 1, 6, 8]) m = np.vstack((x, y, z)) result = np.cov(m, ddof=0) print(result) #---------------------------------------------------------------------------------------------------------------------------- import numpy as np x = np.array([1, 2, 3, 4, 5]) y = np.array([3, 6, 8, 10, 12]) z = np.array([3, 5, 1, 6, 8]) m = np.vstack((x, y, z)) result = np.cov(m, ddof=0) print(result) m = np.cov((x, y, z), ddof=0) print(m) #---------------------------------------------------------------------------------------------------------------------------- Aşağıdaki örnekte iki değişkenin arasındaki ilişkiyi değiştirerek doğrusallık temelinde kovaryans değerlerini inceleyiniz. Değişkenlerin arasındaki ilişki doğrusallığa benzedikçe kovaryans a dyü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() #---------------------------------------------------------------------------------------------------------------------------- Yukarıda da belirttiğimiz gibi 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. Ancak fonksiyonunun rowvar parametresi False yapılırsa bu durumda her sütun ayrı bir değişken kabul edilmektedir. Ö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]]) #---------------------------------------------------------------------------------------------------------------------------- #---------------------------------------------------------------------------------------------------------------------------- 95. Ders - 19/01/2025 - Pazar #---------------------------------------------------------------------------------------------------------------------------- #---------------------------------------------------------------------------------------------------------------------------- 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. Örneğin biz hava sıcaklığı ile yağış miktarı arasındaki kovaryansa baktığımızda sıcaklığı derece olmaktam çıkartıp Fahrenayt haline getirirsek kovaryans değişir. İşte kovaryansların standardize edilmiş haline "Pearson Korelasyon Katsayısı (Pearson Correlation Coefficient)" denilmektedir. Korelasyon katsayıları için aslında 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. İki değişkenin kovaryanslarının standart sapmalarına bölünmesi aslında sütunların standart ölçeklemeye sokulduktan sonra kovaryanslarının hesaplanması anlamına da gelmektedir. 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 korelasyon katsayısı +1 ya da -1'e o kadar yaklaşır. Bir değişken artarken diğeri de artıyorsa pozitif bir korelasyon, bir değişken artarken diğeri azalıyorsa negatif bir korelasyon söz konusudur. 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şı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.8–1 > ise çok yüksek korelasyon İki olgu arasında yüksek bir korelasyon olamsı bunlar arasında bir neden-sonuç ilişkisinin olacağı anlamına gelmemektedir. İki olgu arasında dolaylı birtakım ilişkiler de söz konusu olabilir ancak bu neden-sonuç ilişkisi olmayabilir. Örneğin dondurma satışlarıyla boğulma vakalarının sayısı arasında yüksek bir korelasyon olabilir. Ancak biz buradan dondurma yemenin boğulmaya yol açtığı gibi bir sonuç çıkartamayız. Fakat ne olursa olsun özellikle sosyal bilimlerde ve sağlık bilimlerinde iki olgu arasındaki yüksek korelasyon araştırmacıya bir ipucu verebilmektedir. Araştırmacı buradan hareketle çeşitli hipotezler geliştirip onları test edebilir. Pearson korelasyon katsayısı NumPy'da corrcoef isimli fonksiyonla hesaplanabilmektedir. corrcoef fonksiyonu tamamen cov fonksiyonu gibi kullanılmaktadır. Bu fonksiyon değişkenlerin birbirlerine göre korelasyonlarından oluşan 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 değişkenin 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ü zaten bölme işleminde kullanılan ddof değerleri birbirini götürmektedir. #---------------------------------------------------------------------------------------------------------------------------- #---------------------------------------------------------------------------------------------------------------------------- Makine öğrenmesinde ve veri biliminde 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 etkilere yol açmaktadır. Fazlaca sütun bilgisinin işlenmesi için daha fazla bellek alanına gereksinim duyulmaktadır. Ancak veri kümesinde çok fazla sütunun bulumasının en önemli dezavantajı "overfitting ve underfitting" eğilimini artırmasıdır. Denetimli öğrenmede karmaşık modeller öğrenmenin düşmesine ve yanşlış öğrenmelere yol açabilmektedir. O halde çok fazla sütunun daha az sütuna indirgenmesi önemli önişlem faaliyetlerinden biridir. Buna "boyutsal özellik indirgemesi (dimensionality feature reduction)" denilmektedir. Boyutsal özellik indirgemesi çeşitli Auto ML araçları tarafından otomatik da yapılabilmektedir. Tabii bunun için verilerin iyi 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) yeni 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 eksik veri bakımından 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. n = k + m 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 belirleyip 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 Prices (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 (iris)" 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 verilir. Sonra fit_transform işlemiyle indirgeme yapılır. fit işleminden sonra sınıfın variances_ örnek özniteliğinde sütun varyansları bulunur. Örneğin: from sklearn.feature_selection import VarianceThreshold vt = VarianceThreshold(0.04) reduced_dataset_x = vt.fit_transform(dataset_x) Bu sınıf kendi içerisinde özellik ölçeklemesi yapmamaktadır. Bu nedenle sütunların skalaları birbirinden farklıysa önce özellik ölçeklemesinin yapılması gerekir. Aşağıdaki örnekte Boston Housing Prices 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') print(dataset_x.shape) 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. Bu yönteme "yüksek korelasyon filtrelemesi" denilmektedir. 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 bu işlemden 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 da bir önemi yoktur. Pozitif yüksek korelasyon da negatif yüksek korelasyon da neticede aynı durumlara yol açmaktadır. Scikit-leran içerisinde ya da yaygın kütüphanelerin içerisinde yüksek korelasyon filtrelemesi yapan hazır bir fonksiyon ya da sınıf bulunmamaktadır. Programcının bu işlemi manuel bir biçimde yapması gerekmektedir. 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. Konuya hakimse 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 veri 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 ve 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 bir listede toplanmış sonra da bu sütunlar veri kümesinden atılmıştır. #---------------------------------------------------------------------------------------------------------------------------- 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(i): if i not in eliminated_features and k not in eliminated_features and feature_corrs[i, k] > CORR_THRESHOLD: eliminated_features.append(k) print(eliminated_features) reduced_dataset_x = np.delete(dataset_x, eliminated_features, axis=1) print(reduced_dataset_x.shape) #---------------------------------------------------------------------------------------------------------------------------- 96. Ders - 25/01/2025 - Cumartesi #---------------------------------------------------------------------------------------------------------------------------- #---------------------------------------------------------------------------------------------------------------------------- Yukarıdaki gibi yüksek korelasyon filtrelemesi yapan algoritmada bir noktaya dikkatinizi çekmek istitoruz. Örneğin A ile B arasında ve B ile C arasında yüksek korelasyon olsun. Yukarıdaki algoritmada bunlardan yalnızca bir tanesi (örneğin B sütunu) veri kümesinden atılacaktır. Eğer bunların biri dışında hepsinin atılmasını istiyorsanız bir graf oluşturup o graftaki tek sütunu alıp diğerlerini atma yoluna gidebilirsiniz. Bu durumda muhafaza edilecek sütun hedef değişkenle korelasyonu en yüksek sütun olarak seçilebilir. #---------------------------------------------------------------------------------------------------------------------------- #---------------------------------------------------------------------------------------------------------------------------- En çok kullanılan boyutsal özellik indirgemesi yöntemi "temel bileşenler analizi (principle component analysis)" denilen yöntemdir. Bu yöntemde 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 "denetimsiz (unsupervied)" bir özellik indirgeme yöntemidir. Yani bu yöntemde hedef değişken (dataset_y) ile ilgili bir işlem yapılmamaktadır. Bu yöntemde veri kümesindeki sütunların nümerik olması gerekmektedir. #---------------------------------------------------------------------------------------------------------------------------- #---------------------------------------------------------------------------------------------------------------------------- Temel Bileşenler Analizinin matemetiksel açıklamasının biraz karmaşık olduğunu belirtmiştik. Yöntemin anlaşılabilmesi için "öz değerler ve öz verktörler" konusunun bilinmesi gerekir. Bu yöntemde amaç n boyutlu uzaydaki noktaları k < n olacak biçimde k boyutlu uzaya indirgemektir. Bu indirgeme sırasında k boyutlu uzay için eksen kaydırması ve projeksiyon yapılmaktadır. Projeksiyon n boyutlu uzaydaki noktanın k boyutlu uzaya kaydırılması anlamına gelmektedir. Bu projeksiyon yapılırken projeksiyon sonucunda elde edilen noktaların toplam varyansalarının en yükske olmasına çalışılır. Biz burada bu işlemin yapılabilmesi için gerekli adımları aşağıda vereceğiz. Konun matematiksel ayrıntılaır için başka kaynaklara başvurabilirsiniz: Örneğin n tane sütuna sahip bir veri tablosundan k tane sütuna sahip (k < n) bir veri tablosunu temel bileşenler analizi yöntemiyle elde etmek isteyelim. İşlem adımları şöyle gerçekleştirilir: 1) Önce N sütunlu veriler üzerinde gerekli özellik ölçeklendirmesi uygulanır. 2) n sütunlu matristen nxn'lik kovaryans matrisi elde edilir. 3) Bu kovaryans matrisinden n tane "öz vektör (eigenvector)" bulunur. 4) Bu n tane özvektör arasından k tanesi seçilerek (seçimin nasıl yapılacağı belirtilecektir) asıl matrisle çarpılır ve böylece sonuçta k tane sütuna indirgenmiş veri tablosu elde edilir. Şimdi bu işlemleri adım adım Python'da yapalım. Bu örneğimizde iki sütunlu tabloyu tek sütuna indirgemeye çalışacağız. İki sutunlu tablonun bilgileri şöyle olsun: x1 x2 0.72 0.13 0.18 0.23 2.5 2.3 0.45 0.16 0.04 0.44 0.13 0.24 0.30 0.03 2.65 2.1 0.91 0.91 0.46 0.32 Bu bilgilerin "dataset.csv" isimli dosyada bulunduğunu varsayacağız. Şimdi ilk yapılacak şey bir özellik ölçeklemesi uygulamaktır: ss = StandardScaler() scaled_dataset = ss.fit_transform(dataset) Şimdi orijin noktasını değerlerin ortasına kaydıralım. Bu işlem şöyle yapılabilir: pca_dataset = scaled_dataset - np.mean(scaled_dataset, axis=0) Şimdi kovaryans matrisini elde edelim: cmat = np.cov(pca_dataset, rowvar=False) Şimdi de kovaryans matrisiin özdeğerlerini ve özvektörlerini elde edelim: evals, evects = np.linalg.eig(cmat) Şimdi bizim projeksiyon işlemini yapmamız gerekir. Biz ölçeklendirilmiş asıl matrisimizi nxn'lik özvektör matrisinin k tanesiyle çarptığımızda artık k tane sütunlu bir matris elde ederiz. Buradaki amacımız iki sütunu tek sütuna indirgemekti. Demek ki biz tek bir özvektörle çarpma yaparak tek sütunumuzu elde edeceğiz. Pekiyi n tane öz vektör arasından hangi k tane özvektörü bu çarpma işlemine sokmalıyız? İşte öz değeri yüksek vektörlerin bu işlem için seçilmesi gerekmektedir. Özdeğeri yüksek olan vektörü (yani sütun indeksini) şöyle elde edebiliriz: max_index = np.argmax(evals) Şimdi de biz bu özvektörü asıl matrisle çarpmalıyız. reduced_dataset = np.matmul(pca_dataset, evects[:, max_index].reshape((-1, 1))) Kod bir bütün olarak aşağıda verilmiştir. #---------------------------------------------------------------------------------------------------------------------------- import numpy as np import pandas as pd df = pd.read_csv('dataset.csv') dataset = df.to_numpy('float32') from sklearn.preprocessing import StandardScaler ss = StandardScaler() scaled_dataset = ss.fit_transform(dataset) pca_dataset = scaled_dataset - np.mean(scaled_dataset, axis=0) cmat = np.cov(pca_dataset, rowvar=False) evals, evects = np.linalg.eig(cmat) max_index = np.argmax(evals) reduced_dataset = np.matmul(pca_dataset, evects[:, max_index].reshape((-1, 1))) print(reduced_dataset) #---------------------------------------------------------------------------------------------------------------------------- Temel bileşenler analizi scikit-learn kütüphanesinde 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ı belirtmektedir. PCA nesnesi yaratıldıktan sonra önce fit işlemi yapılır. Bu işlem sırasında indirgemede kullanılacak bilgiler elde edilir. Ondan sonra gerçek indirgeme transform metoduyla yapılmaktadır. Tabii fit ve transform işlemleri bir arada da fit_transform metoduyla yapılabilmektedir. Yukarıda da belirttiğmiz gibi 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. Yukarıda manuel olarka yaptığımız PCA işlemi aşağıda scikit-learn içerisindeki PCA sınıfıyla gerçekleştirilmiştir. Her iki işlem sonucunda aynı değerlerin elde edildiğine dikkat ediniz. #---------------------------------------------------------------------------------------------------------------------------- import pandas as pd df = pd.read_csv('dataset.csv') dataset = df.to_numpy('float32') from sklearn.preprocessing import StandardScaler ss = StandardScaler() scaled_dataset = ss.fit_transform(dataset) from sklearn.decomposition import PCA pca = PCA(1) reduced_dataset = pca.fit_transform(scaled_dataset) print(reduced_dataset) #---------------------------------------------------------------------------------------------------------------------------- Aşağıdaki örnekte "Boston Housing Prices" 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) reduced_dataset_x = pca.fit_transform(scaled_dataset_x) print(reduced_dataset_x) print(dataset_x.shape) print(reduced_dataset_x.shape) #---------------------------------------------------------------------------------------------------------------------------- Şüphesiz PCA işlemiyle indirginen veri kümesi ile oluşturulan modelde "predict" işlemi yapılırken "predict" işlemi için kullanılacak veri kümesini yine özellik indirgemesine sokarak eğitim sırasındaki veri kümesi biçimine getirmek getirmemiz gerekir. Yukarıdaki örnekte biz önce StandardScaler ve sonra da PCA işlemlerini uyguladık. Muhtemelen asıl modelimizde 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ümesi eitimde kullanılan StandardScaler ve PCA bilgilertiyle transform edilmelidir. Aşağıdaki örnekte 13 sütundan oluşan "Boston Housing Prices" veri kümesi önce StandardScaler sınıfı ile ölçeklendirilmiştir. Daha sonra standardize 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ı açı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) #---------------------------------------------------------------------------------------------------------------------------- PCA sınıfında fit işlemi sonucunda nesne üzerinde bazı öznitelikler oluşturumaktadır. Bu öznitelikler PCA işleminin matematiksel temeline ilişkin bilgiler vermektedir. Burada uygulamacı için en önemli iki öznitelik explained_variance_ ve explained_variance_ratio_ öznitelikleridir. PCA işlemi sonucunda elde edilen "açıklanan varyans (explained variance)" değeri dönüştürmedeki kayıp hakkında bilgi veren en önemli göstergedir. Açıklanan varyans oranları indirgenmiş sütunların asıl veri kümesini temsil etme kuvvetini belirtmektedir. Ö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.Açıklanan varyans oranlarının toplam 1 olmaz. Çünkü indirgemede bir kayıp da söz konusudur. Açıklanan varyans oranları indirgenmiş sütunların asıl veri kümesini açıklamakta ne kadar etkili olduğu konusunda da bize fikir vermektedir. Örneğin yukarıdaki "Boston Housing Prices" veri kümesini 10 sütuna indirgedidiğimizde bu sütunların açıklanan varyansları PCA sınıfının explained_variance_ratio_ özniteliğinden şöyle elde edilmiştir: array([0.47011107, 0.10884895, 0.09291499, 0.06930587, 0.06372181, 0.05212396, 0.04226861, 0.03040597, 0.02087826, 0.01714009], dtype=float32) Bu değerlerin toplamı 0.9677195865660906 biçimindedir. BU açıklanan varyans oranlarına baktığımızda örneğin ilk sütunun en önemli bilgiyi barındırdığı görülmektedir. #---------------------------------------------------------------------------------------------------------------------------- #---------------------------------------------------------------------------------------------------------------------------- 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? 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 Housing Prices veri kümesi için bu işlemi 1'den başlayarak n'e kadar tek tek yapalım: 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}') 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 için 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 görsel bir biçimde kararını verir. Birinci yöntem özetle aşağıdaki kodda olduğu gibi uygulanabilir: TARGET_RATIO = 0.7 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 Aşağıda bu yöntemin uygulanışına 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 nokta gözle tespit edilir. Buna tıpkı kümelemede olduğu gibi "dirsek noktası (elbow point)" da denilmektedir. 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şlemi çok sütunlu olan veri kümelerinin iki boyutlu grafiklerinin çizilmesi için de kullanılmaktadır. Biz de zaten daha önceki örneklerimizde "zambak (iris)" veri kümesini kümeledikten sonra bu yöntemle grafiğini çizmiştik. #---------------------------------------------------------------------------------------------------------------------------- #---------------------------------------------------------------------------------------------------------------------------- Özellik seçimi ve özwllik mühendisliği için kullanılan alternatif birtakım kütüphaneler de vardır. Bu kütüphanelerin bazıları zaman içerisinde hızlı bir gelişim göstermiştir. (Makine öğrenmesi ve veri bilimi çok yüksek ivmeli bir alan olduğu için bir sene içerisinde bile kütüphanelerde ve yöntemlerde yeni gelişmeeler yaşanabilmektedir.) Özellik seçimi ve mühendisliği için son yıllarda gelime göstermiş olan kütüphanelerden biri de "feature-engine" isimli kütüphanedir. Kütüphane şöyle yüklenebilir: pip install feature-engine Feaure-engine kütüphasindeki bazı özellikler zaten scikit-learm kütüphanesinde de bulunmaktadır. Ancak bu kütüphanenin asıl yoğunlaştığı alan özellik seçimi ve mühendisliği olduğu için bu bağlamda scikit-learn kütüphanesinden daha fazla yeteneğe sahiptir. Biz burada kütüphanin kullanımı için bir örnek üzerinde duracağız. Kütüphanenin ana paket ismi feature_engine biçimindedir. Örneğin: import feature_engine import fe Kütüphanenin genel arayüzü scikit-learn kütüphanesine benzetilmiştir. Yani önce bir sınıf nesnesi yaratılır. Sonra fit ve transform işlemleri yapılır. #---------------------------------------------------------------------------------------------------------------------------- #---------------------------------------------------------------------------------------------------------------------------- Örneğin feature-engine kütüphanesindeki DropCorrelatedFeatures sınıfı yüksek korelasyon filtrelemesi yapmaktadır. Biz bu sınıf türünden bir nesne yaratırken korelasyon için eşik değerini veririz. Korelasyon katsayısının nasıl hesaplanacağını (tipik olarak "pearson") fonksiyonda belirtiriz. Sonra fit ve transform işlemlerini yaparız. Örneğin: CORR_THRESHOLD = 0.70 from feature_engine.selection import DropCorrelatedFeatures dcf = DropCorrelatedFeatures(method='pearson', threshold=CORR_THRESHOLD) reduced_dataset_x = dcf.fit_transform(df) Burada 0.70'lik bir eşik değeri seçildiğinde Boston Housing Prices veri kümesi 10 sütuna indirgenmiştir. #---------------------------------------------------------------------------------------------------------------------------- CORR_THRESHOLD = 0.70 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 feature_engine.selection import DropCorrelatedFeatures dcf = DropCorrelatedFeatures(method='pearson', threshold=CORR_THRESHOLD) reduced_dataset_x = dcf.fit_transform(df) print(reduced_dataset_x.shape) #---------------------------------------------------------------------------------------------------------------------------- Boyutsal özellik indirgemesi için kullanılan diğer bir yönteme "Doğrusal Ayırtaç Analizi (Linear Discriminant Analysis)" denilmektedir. Biz bu yöntemi İngilizce LDA biçiminde ifade edeceğiz. (LDA İngilizce "Linear Discriminant Analysis) sözcüklerinin ilk harflerinden yapılan kısaltmadır.) LDA sınıflandırma amacıyla oluşturulmuş veri kümelerinde denetimli (supervised) biçimde uygulnan bir yöntemdir. Bu yöntemde n boyutlu uzaydaki noktalar k < n olacak biçimde k boyutlu uzaydaki noktalara dönüştürülmektedir. Dönüştürmede noktaların kovaryans yapısı ya da sınıf merkezleri dikkate alınmaktadır. Dolayısıyla veri kümesindeki özelliklerin (sütunların) sayısal olması gerekir. LDA yöntemi yalnızca özellik indirgemesi için değil aynı zamanda sınıflandırma amacıyla da kullanılabilmektedir. Çünkü bu yöntemde aslında noktaları birbirinden ayırmak için doğrusal vektörler oluşturulmaktadır. Biz burada LDA yöntemini özellik indirgemesi amacıyla kullanacağız. Yöntemin sınıflandırma amacıyla kullanılması ileride ayrı bir bölümde ele alınmaktadır. LDA işlemi tipik olarak şu adımlarla gerçekleştirilmektedir: 1) Önce veri kümesi üzerinde özellik ölçeklemesi yapılır. Yöntemde kovaryans hesabı kullanıldığı için standart ölçekleme tercih edilmektedir. Örneğin: ss = StandardScaler() scaled_dataset_x = ss.fit_transform(dataset_x) 2) Her sınıf için o sınıfa dahil olan noktaların (satırların) sütunsal ortalaması elde edilir. Ortalama işlemi K-Means yöntemindeki ağırlık merkezinin elde edilmesinde olduğu gibi yapılmaktadır. Örneğin: nfeatures = dataset_x.shape[1] labels = np.unique(dataset_y) mean_overall = np.mean(scaled_dataset_x, axis=0) mean_list = [] for label in labels: mean_list.append(np.mean(scaled_dataset_x[dataset_y == label], axis=0)) 3) Sınıf İçi Saçılım Matrisi (Within-Class Scatter Matrix) oluşturulur. Bu matrise Sw matrisi ya da S_W matrisi de denilmektedir. Sınıf içi saçılım matrisi, her sınıfın kendi merkezine (ortalamasına) göre ne kadar saçıldığını ölçer. LDA bağlamında sembolik bir biçimde şu biçimde tanımlanır: S_W = sum((dataset_xi - Mi) * (dataset_xi - Mi)^T) Burada dataset_xi veri kümesindeki i'inci sınıfa ilişkin satırları Mi ise i'inci sınıfa ilişkin satır ortalamasını, ^T ise transpoze işlemini belirtmektedir. dataset_xi - Mi işlemi veri kümesinin her satırının kendi sütun ortalamasından çıkartılması anlamına gelmektedir. Bir vektörün kendi transpozesi ile çarpılmasının "dot product" anlamına geldiğine dikat ediniz. Bu işlem aşağıdaki gibi bir döngüyle yapılabilir: S_W = np.zeros((nfeatures, nfeatures)) for label, mvect in zip(labels, mean_list): scatter = np.zeros((nfeatures, nfeatures)) for row in scaled_dataset_x[dataset_y == label]: r = row.reshape(nfeatures, 1) m = mvect.reshape(nfeatures, 1) scatter += (r - m).dot((r - m).T) S_W += scatter 4) Sınıflararası Saçılım Matrisi (Between-Class Scatte Matrix) oluşturulur. Bu matris sınıf içi saçılım matrisi gibi hesaplanmaktadır. Yalnız ortalamalarda sınıf ortalamaları değil tüm noktaların ortalalamaları kullanılır: S_B = sum(Ni (dataset_xi - M) * (dataset_xi - M)^T) Buradaki Ni i'inci sınıftaki nokta sayısını M ise tüm veri kümesinin ortalamasını belirtmektedir. Bu işlem aşağıdaki kodla yapılabilir: S_B = np.zeros((nfeatures, nfeatures)) mo = mean_overall.reshape(nfeatures, 1) for label, mvect in zip(labels, mean_list): n = scaled_dataset_x[dataset_y == label].shape[0] m = mvect.reshape(nfeatures, 1) S_B += n * (mvect - mo).dot((mvect - mo).T) 5) S_W^-1 * S_B matrisi oluşturulur. Nu işlem kodla şöyle yapılabilir: S_W_inv = np.linalg.pinv(S_W) S = S_W_inv.dot(S_B) 6) Elde edilen bu matristen hareketle özvektörler bulunur. 7) Bulunan özvektörler o sınıftaki satırlarla (noktalarla) çarpılır. #---------------------------------------------------------------------------------------------------------------------------- #---------------------------------------------------------------------------------------------------------------------------- LDA yöntemi scikit-learn içerisindeki discriminant_analysis modülü içerisinde bulunan LinearDiscrimiantAnalysis isimli sınıfla yapılmaktadır. Sınıfın __init__ metodunun parametrik yapısı şöyledir: class sklearn.discriminant_analysis.LinearDiscriminantAnalysis(solver='svd', shrinkage=None, priors=None, n_components=None, store_covariance=False, tol=0.0001, covariance_estimator=None) Buradaki parametrelerin yöntemin matematiksel işleyişi ile ilgilidir. Ancak n_components parametresi özellik indirgemesi uygulanacaksa hedeflenen özellik sayısını belirtmektedir. n_components parametresi toplam sınıf sayısından küçük olmalıdır. Örneğin "zambak (iris)" veri kümesinde toplam 3 tane sınıf vardır. Bu nedenle bu veri kümesinde n_components < 3 olmak zorundadır. LinearDiscriminantAnalysis nesnesi oluşturulduktan sonra dataset_x ve dataset_y değerleriyle fit işlemi yapılır. Sonra da transform metodu ile dönüştürme geröekleştirilir. Yukarıda da belirttiğimiz gibi yöntemi uygulamadan önce bir özellik ölçeklemesinin yapılması gerekmektedir. Örneğin: ss = StandardScaler() ss.fit(dataset_x) scaled_dataset_x = ss.transform(dataset_x) lda = LinearDiscriminantAnalysis(n_components=NFEATURES) lda.fit(scaled_dataset_x, dataset_y) transformed_dataset_x = lda.transform(scaled_dataset_x) Aşağıda yöntemin "zambak (iris)" veri kğmesi üzerinde uygulanmasına örnek verilmiştir. Burada 4 sütun, 3 sınıftan oluşan zambak veri kümesi 2 sütuna indirgenmiştir. #---------------------------------------------------------------------------------------------------------------------------- NFEATURES = 2 import pandas as pd df = pd.read_csv('iris.csv') dataset_x = df[['SepalLengthCm','SepalWidthCm','PetalLengthCm','PetalWidthCm']].to_numpy('float32') dataset_y = df['Species'] from sklearn.discriminant_analysis import LinearDiscriminantAnalysis from sklearn.preprocessing import StandardScaler ss = StandardScaler() ss.fit(dataset_x) scaled_dataset_x = ss.transform(dataset_x) lda = LinearDiscriminantAnalysis(n_components=NFEATURES) lda.fit(scaled_dataset_x, dataset_y) transformed_dataset_x = lda.transform(scaled_dataset_x) print(transformed_dataset_x.shape) #---------------------------------------------------------------------------------------------------------------------------- 97. Ders - 26/01/2025 - Pazar #---------------------------------------------------------------------------------------------------------------------------- #---------------------------------------------------------------------------------------------------------------------------- Makine öğrenmesine ilişkin kütüphanelerin çoğunda "boru hattı (pipeline)" denilen bir mekanizma vardır. Programcı peşi sıra yapılacak işlemleri bu boru hattına verir, sonra tek bir metot çağırarak bu işlemlerin peşi sıra yapılmasını sağlar. Bu da kodun daha sade gözeükmesini sağlamaktadır. Scikit-leran kütüphanesinde de Keras kütüphanesinde de boru hattı mekanizması vardır. Tabii boru hattı mekanizması için sınıfların belli metotlara sahip olması gerekir. Örneğin biz bir veri kümesi üzerinde scikit-learn kullanarak K-Means kümeleme işlemini yapmak isteyelim. Ancak veri kümemizde eksik veriler de bulunuyor olsun. Biz bunun için önce SimpleImputer sınıfı ile imputation yapıp bunun sonucunu StandardScaler sınıfı ile standardizasyon yapabiliriz. Bunun da sonucunu KMeans sınıfına verebiliriz. Burada çıktının girdiye verildiği bir dizi işlem söz konusudur. İşte bu tür durumlarda boru hattı mekanizması kodlamyı kısaltmaktadır. Biz buradaki örneği boru hattı mekanizmasını kullanmadan aşağıdaki gibi tek tek yapabiliriz: si = SimpleImputer(strategy='mean') si.fit(dataset) output = si.transform(dataset) ss = StandardScaler() ss.fit(output) output = si.transform(output) km = KMeans(3) km.fit(output) final_output = km.tranform(output) Zaten biz şimdiye kadar hep bu yöntemi izledik. İşte bu işlem boru hattı yoluyla aslında aşağıdaki gibi de yapılabilmektedir: pl = Pipeline([('imputation', SimpleImputer(strategy='mean')), ('scaling', StandardScaler()), ('clustering', KMeans(3))]) pl.fit(dataset) output = pl.transform(dataset) #---------------------------------------------------------------------------------------------------------------------------- #---------------------------------------------------------------------------------------------------------------------------- scikit-learn içerisindeki Pipeline sınıfı skleran.pipeline modülü içerisinde bulunmaktadır. Sınıfın __init__ metodunun parametrik yapısı şöyledir: class sklearn.pipeline.Pipeline(steps, *, memory=None, verbose=False) Buradaki steps parametresi ikili demetlerden oluşan bir liste biçiminde girilir. İki elemanlı demetlerin ilk elemanı uygulamacının kendi belirlediği bir isimden ikinci elemanı ise dönüştürücü nesneden oluşmalıdır. Örneğin: steps = [('imputation', SimpleImputer(strategy='mean')), ('scaling', StandardScaler()), ('clustering', KMeans(3))] pl = Pipeline(steps) Metodun memory parametresi "cache'leme" yapılıp yapılmayacağını belirtmektedir. Default durumda bir cache'leme yapılmamaktadır. Pipeline sınıfı türünden nesne yaratıldıktan sonra uygulamacı fit ve transform işlemlerini yapar. Tabii yine sınıfın fit_transform metodu da vardır. fit ve transform işlemlerinde bir döngü içerisinde önceki nesnenin fit ve transform çıktıları sonrakine verilmektedir. transform işleminden en son nesnenin çıktısı elde edilmektedir. scikit-learn kütüphanesinde kullanılan terminolojide boru hattına verilen her nesneye "dönüştürücü (transformer)" denilmektedir. (Buradaki "dönüştürücü (tranformer)" kavramı ile LLM'lerde kullanılan "dönüştürücü (tranformer)" kavramının bir ilişkisi yoktur.) scikit-learn kütüphanesinde son dönüştürücüye de "nihai tahminleyici (final estimator)" denilmektedir. Pekiyi buradaki dönüştürü nesnelere neden isim verilmektedir? İşte isimler bu nesnelerin daha sonra elde edilmesi için kullanılmaktadır. Pipeline sınıfının named_steps isimli property elemanı bize boru hattındaki dönüştürücü nesneleri isimleri ile vermektedir. named_steps property'si bir sözlük biçimindedir. Bu sözlüğün anahtarları isimlerden değerleri ise dönüştürücü nesnelerden oluşmaktadır. Pipeline sınıfı için __getitem__ metodu yazılmıştır. Biz herhangi bir indeksteki dönüştürücü nesneye indeks numarasını vererek [] operatörü ile de erişebiliriz. Pekiyi Pipeline sınıfının fit metodu nasıl fit işlemi yapmaktadır? Önceki dönüştürücü nesnenin çıktısı sonraki dönüştürücü nesnenin argüman yapılacağına göre aslında fit işlemi transform olmadan yapılamaz. Gerçekten de biz Pipeline nesnesi üzerinde fit işlemi yaparken aslında PipeLine nesnesi dönüştürücüler üzerinde fit_transform metotlarını uygulamaktadır. Nesne Yönelimli Programlama Tekniğinde Pipeline sınıfında uygulanan tasarım kalıbına "bileşim (composite)" kalıbı denilmektedir. Bileşim (compoiste) kalbınında bileşim işlemini uygulayan sınıf da aynı metotlara sahip olduğu için başka bir bileşim nesnesinde kullanılabilmektedir. Yani biz birkaç tane Pipeline nesnesini de başka Pipeline nesnelerinde dönüştürücü olarak kullanabiliriz. Örneğin: pl1 = Pipeline(....) pl2 = Pipeline(....) pl3 = Pipeline(....) final_pl = Pipeline([('pl', pl1), ('pl2', pl2), ('pl3', pl3)]) #---------------------------------------------------------------------------------------------------------------------------- #---------------------------------------------------------------------------------------------------------------------------- Şimdi SimpleImputer, StandardScaler ve KMeans nesnelerini bir boru hattında birleştiren bir örnek yapalım. Bunun için Pipeline nesnesini şöyle oluşturulabiliriz: steps = [('imputation', SimpleImputer(strategy='mean')), ('scaling', StandardScaler()), ('clustering', KMeans(3))] pl = Pipeline(steps) Şimdi biz pl nesnesi ile fit işlemi yaptığımızda tüm dönüştürücüler peşi sıra fit edilecektir: pl.fit(dataset) transform işlemi yaptığımızda da tüm nesneler peşi sıra transform edilecektir: distances = pl.transform(dataset) print(distances) Biz burada nihai dönüştürücüyü (final estimator) named_steps property'si yoluyla elde edebiliriz: km = pl.named_steps['clustering'] Sonra da onun elemanlarına erişebiliriz: print(km.labels_) Aslında Pipeline sınıfının __getitem__ metodu da yazılmış durumdadır. Yani biz bir dönüştürücüye [] operatörü ile de indeks numarası vererek erişebiliriz: print(pl[2].labels_) Aşağıda kod bir bütün olarak verilmiştir. #---------------------------------------------------------------------------------------------------------------------------- import pandas as pd df = pd.read_csv('iris.csv') dataset = df[['SepalLengthCm','SepalWidthCm','PetalLengthCm','PetalWidthCm']].to_numpy('float32') from sklearn.impute import SimpleImputer from sklearn.preprocessing import StandardScaler from sklearn.cluster import KMeans from sklearn.pipeline import Pipeline steps = [('Imputation', SimpleImputer(strategy='mean')), ('Scaling', StandardScaler()), ('Clustering', KMeans(3))] pl = Pipeline(steps) distance = pl.fit_transform(dataset) print(distances) km = pl.named_steps['Clustering'] print(km.labels_) print(pl[2].labels_) #---------------------------------------------------------------------------------------------------------------------------- Pipeline sınıfının memory parametresi ve diğer bazı özellikleri olmadan basit bir gerçekleştirimi şöyle olabilir: class SimplePipeline: def __init__(self, steps): self._steps = steps self.named_steps = dict(steps) def fit(self, X, y=None): for name, step in self._steps: X = step.fit_transform(X, y) return self def transform(self, X): for name, step in self._steps: X = step.transform(X) return X def fit_transform(self, X, y=None): for name, step in self._steps: X = step.fit_transform(X, y) return X def __getitem__(self, index): return self._steps[index][1] def predict(self, X): last_step = self._steps[-1][1] return last_step.predict(X) Aşağıda kendi yazdığımız sınıfın kullanılmasına ilişkin bir örnek verilmiştir. #---------------------------------------------------------------------------------------------------------------------------- class SimplePipeline: def __init__(self, steps): self._steps = steps self.named_steps = dict(steps) def fit(self, X, y=None): for name, step in self._steps: X = step.fit_transform(X, y) return self def transform(self, X): for name, step in self._steps: X = step.transform(X) return X def fit_transform(self, X, y=None): for name, step in self._steps: X = step.fit_transform(X, y) return X def __getitem__(self, index): return self._steps[index][1] def predict(self, X): # Son adımda tahmin yapılması varsayılır (örneğin, modelin son adımı) last_step = self._steps[-1][1] return last_step.predict(X) import pandas as pd df = pd.read_csv('iris.csv') dataset = df[['SepalLengthCm','SepalWidthCm','PetalLengthCm','PetalWidthCm']].to_numpy('float32') from sklearn.impute import SimpleImputer from sklearn.preprocessing import StandardScaler from sklearn.cluster import KMeans steps = [('Imputation', SimpleImputer(strategy='mean')), ('Scaling', StandardScaler()), ('Clustering', KMeans(3))] pl = SimplePipeline(steps) pl.fit(dataset) distances = pl.transform(dataset) print(distances) km = pl.named_steps['Clustering'] print(km.labels_) print(pl[2].labels_) #---------------------------------------------------------------------------------------------------------------------------- Pipeline nesnesindeki dönüştürücülerin parametrelerini de elde edip değiştirebiliriz. Bunun sınıfta get_params ve set_params metotları kullanılmaktadır. Bu metotlarda dönüştürücü isminin yanına __ getirerek parametre ismi belirtilerek set işlemi yapılabilir. Örneğin: steps = [('Imputation', SimpleImputer(strategy='mean')), ('Scaling', StandardScaler()), ('Clustering', KMeans(3))] pl = Pipeline(steps) ... pl.set_params(Clustering__n_clusters=2) distances = pl.fit_transform(dataset) print(distances) #---------------------------------------------------------------------------------------------------------------------------- #---------------------------------------------------------------------------------------------------------------------------- Pipeline nesnesini daha kolay oluşturabilmek için sklearn.pipeline modülünde make_pipeline isimli bir fonksiyon da bulundurulmuştur. Fonksiyonun parametrik yapısı şöyledir: sklearn.pipeline.make_pipeline(*steps, memory=None, verbose=False) Burada programcı dönüştürücü nesneleri ayrı parametreler biçiminde fonksiyona verir. Fonksiyon bu nesnelerin sınıf isimlerini küçük harfe dönüştürerek isimleri oluşturur ve Pipeline nesnesini yaratarak ona geri döner. Fonksiyonun basit gerçekletirimi aşağıdaki gibi olabilir: def mymake_pipeline(*steps, memory=None, verbose=False): corrected_steps = [(type(step).__name__.lower(), step) for step in steps] return Pipeline(corrected_steps, memory=memory, verbose=verbose) Örneğin: pl = make_pipeline(SimpleImputer(strategy='mean'), StandardScaler(), KMeans(3)) #---------------------------------------------------------------------------------------------------------------------------- import pandas as pd df = pd.read_csv('iris.csv') dataset = df[['SepalLengthCm','SepalWidthCm','PetalLengthCm','PetalWidthCm']].to_numpy('float32') from sklearn.pipeline import make_pipeline from sklearn.impute import SimpleImputer from sklearn.preprocessing import StandardScaler from sklearn.cluster import KMeans pl = make_pipeline(SimpleImputer(strategy='mean'), StandardScaler(), KMeans(3)) pl.fit(dataset) distances = pl.transform(dataset) print(distances) km = pl.named_steps['kmeans'] print(km.labels_) #---------------------------------------------------------------------------------------------------------------------------- Yukarıda da belirttiğimiz gibi eğer biz kendi sınıfımızda fit, transform, fit_transform, predict gibi metotları bulundurmuşsak kendi sınıfımız türünden nesneleri de scikit-leran kütüphanesindeki boru hattı mekanizmasına dahil edebiliriz. Örneğin feature-engine kütüphanesindeki sınıfların da fit, transform, fit_transform, predict gibi metotları olduğu için bunlar boru hattı mekanizmasına dahil edilebilmektedir: pl = make_pipeline(DropCorrelatedFeatures(threshold=0.7), SimpleImputer(strategy='mean'), StandardScaler(), KMeans(3)) Aşağıda MyStandardScaler isimli sarma bir sınıf yazılmıştır. Bu sınıfın ilgili metotları StandardScaler sınıfının ilgili metotlarını çağrımaktadır. Biz bu sınıfı fit, transform ve fit_transform mtotları olduğu içinm boru hattı mekanizmasına dahil edebiliriz: class MyStandardScaler: def __init__(self): self._ss = StandardScaler() def fit(self, X, y=None): return self._ss.fit(X, y) def transform(self, X): return self._ss.transform(X) def fit_transform(self, X, y=None): return self._ss.fit_transform(X, y) pl = make_pipeline(SimpleImputer(strategy='mean'), MyStandardScaler(), KMeans(3)) distances = pl.fit_transform(dataset) #---------------------------------------------------------------------------------------------------------------------------- import pandas as pd df = pd.read_csv('iris.csv') dataset = df[['SepalLengthCm','SepalWidthCm','PetalLengthCm','PetalWidthCm']].to_numpy('float32') from sklearn.pipeline import make_pipeline from sklearn.impute import SimpleImputer from sklearn.preprocessing import StandardScaler from sklearn.cluster import KMeans class MyStandardScaler: def __init__(self): self._ss = StandardScaler() def fit(self, X, y=None): return self._ss.fit(X, y) def transform(self, X): return self._ss.transform(X) def fit_transform(self, X, y=None): return self._ss.fit_transform(X, y) pl = make_pipeline(SimpleImputer(strategy='mean'), MyStandardScaler(), KMeans(3)) distances = pl.fit_transform(dataset) print(distances) km = pl.named_steps['kmeans'] print(km.labels_) #---------------------------------------------------------------------------------------------------------------------------- 98. Ders - 01/02/2025 - Cumartesi #---------------------------------------------------------------------------------------------------------------------------- #---------------------------------------------------------------------------------------------------------------------------- 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 "anomalilerin 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 anamali içeren ve içermeyen bilgiler varsa biz denetimli 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 (unsupervised) öğ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.) - En Yakın Komuşuk Yöntemleri (k-Nearest Neighbors) - 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. #---------------------------------------------------------------------------------------------------------------------------- #---------------------------------------------------------------------------------------------------------------------------- Anomalilerin tespit edilmesinde en çok kullanılan yöntem gruplarından biri "kümeleme tabanlı" yöntem gruplarıdır. Kümeleme tabanlı anomoli tespit sürecinde veri kümesini oluşturan noktalar denetimsiz kümeleme işlemine sokulur. Kümeleme sonucunda kopuk noktalar (gürültü noktaları) tespit edilir. Bunun için daha çok DBSCAN, OPTICS gibi yoğunluk tabanlı kümeleme yöntemleri tercih edilmektedir. Anımsanacağı gibi bu yöntemlerde belli bir eps ve min_samples hyper parametreleri uygulamacı tarafından veriliyor ve bunun sonucunda da kümelemedeki gürültü noktaları elde edilebiliyordu. DBSCAN sınıfında fit işleminden sonra gürültü noktalarının labels_ özniteliğindeki -1 değerleriyle belirtildiğini anımsayınız. Uygulamacı eps ve min_samples değerlerini belirleyerek anomali tespit sıkılığını ya da gevşekliğini ayarlayabilmektedir. Örneğin: EPS = 0.70 MIN_SAMPLES = 5 dbs = DBSCAN(eps=EPS, min_samples=MIN_SAMPLES) dbs.fit(dataset) anomaly_data = dataset[dbs.labels_ == -1] 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 X tespit edilmiş ve grafik üzerinde X sembolüyle gösterilmiştir. Bu örnekte siz de min_samples değerini sabit bırakarak eps değerini değiştirip anomalileri tespit ediniz. #---------------------------------------------------------------------------------------------------------------------------- EPS = 0.70 MIN_SAMPLES = 5 import pandas as pd df = pd.read_csv('iris.csv') dataset = df[['SepalLengthCm','SepalWidthCm','PetalLengthCm','PetalWidthCm']].to_numpy('float32') from sklearn.preprocessing import StandardScaler ss = StandardScaler() ss.fit(dataset) transformed_dataset = ss.transform(dataset) from sklearn.cluster import DBSCAN dbs = DBSCAN(eps=EPS, min_samples=MIN_SAMPLES) dbs.fit(transformed_dataset) import numpy as np nclusters = np.max(dbs.labels_) + 1 from sklearn.decomposition import PCA pca = PCA(n_components=2) reduced_dataset = pca.fit_transform(dataset) import matplotlib.pyplot as plt plt.figure(figsize=(10, 8)) plt.title('Clustered Points') plt.title('DBSCAN Clustered Points', fontsize=12) for i in range(nclusters): plt.scatter(reduced_dataset[dbs.labels_ == i, 0], reduced_dataset[dbs.labels_ == i, 1]) plt.scatter(reduced_dataset[dbs.labels_ == -1, 0], reduced_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)}') #---------------------------------------------------------------------------------------------------------------------------- Anomalilerin tespit edilmesi için K-Means 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 merkezini 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 aslında manuel biçimde de bulabiliriz. Aşağıda zambak verileri için bu yöntem kullanılmıştır. Bu yöntem anomali tespiti için zayıf bir yöntemdir. DBSCAN kümeleme yöntemleri anomali tespiti için daha iyi sonuç vermektedir. #---------------------------------------------------------------------------------------------------------------------------- NANOMALY_POINTS = 5 import numpy as np import pandas as pd df = pd.read_csv('iris.csv') dataset = df[['SepalLengthCm','SepalWidthCm','PetalLengthCm','PetalWidthCm']].to_numpy('float32') from sklearn.preprocessing import StandardScaler ss = StandardScaler() ss.fit(dataset) scaled_dataset = ss.transform(dataset) from sklearn.cluster import KMeans km = KMeans(n_clusters=1, n_init=10) distances = km.fit_transform(scaled_dataset) arg_sorted_distances = np.argsort(distances[:, 0]) anomaly_points = dataset[arg_sorted_distances[-NANOMALY_POINTS:]] print(anomaly_points) import matplotlib.pyplot as plt from sklearn.decomposition import PCA pca = PCA(n_components=2) pca.fit(dataset) reduced_dataset = pca.transform(dataset) reduced_normal_points = reduced_dataset[arg_sorted_distances[:-NANOMALY_POINTS]] reduced_anomaly_points = reduced_dataset[arg_sorted_distances[-NANOMALY_POINTS:]] scaled_centroids = ss.inverse_transform(km.cluster_centers_) reduced_centroids = pca.transform(scaled_centroids) plt.figure(figsize=(10, 8)) plt.title('K-Means Anomaly Detection') plt.scatter(reduced_normal_points[:, 0], reduced_normal_points[:, 1], color='blue') plt.scatter(reduced_anomaly_points[:, 0], reduced_anomaly_points[:, 1], color='red') plt.legend(['Normal Points', 'Anomaly Points']) plt.show() #---------------------------------------------------------------------------------------------------------------------------- Anomali tespiti için "k En Yakın Komşuluk (k-NN) yöntemi" de kullanılabilir. Bu yöntemde her noktanın en yakın N tane komşusu elde edilir. Bu komşuların ilgili noktaya uzaklarının ortalaması hesaplanır. Böylece belli bir eşik değerini aşan noktalar anomali olarak belirlenir. Bunun için scikit-learn kütüphanesindeki NearestNeigbors sınıfından faydalanabiliriz. class sklearn.neighbors.NearestNeighbors(*, n_neighbors=5, radius=1.0, algorithm='auto', leaf_size=30, metric='minkowski', p=2, metric_params=None, n_jobs=None) Metodun n_neighbors parametresi en yakın kaç komuşu noktanın bulunacağını belirtmektedir. metric parametresi yine uzaklık için kullanılacak metriğin ne olduğunu belirtir. Diğer parametreler en yakın komşulukların oluşturulması için kullanılan veri yapısı ile ilgilidir. Bu sınıf "denetimsiz (unsupervised)" bir işlem yapmaktadır. Yani biz fit işleminde yalnızca dataset_x değerlerini veririz. Sınıfın kneighbors metodu verilen noktaların en yakın k komşusunu bulmaktadır. Tabii biz bu metoda aynı dataset_x noktalarını verirsek bu durumda metot bize mevcut noktaların en yakın k komşularını verecektir. Örneğin: nn = NearestNeighbors(n_neighbors=NNEIGHBORS) nn.fit(dataset) distances, indices = nn.kneighbors(dataset) kneighbors metodu ikili bir demete geri dönmektedir. Demetin birinci elemanı verilen noktalara en yakın k tane noktaya olan uzaklıkları verir. İkinci elemanı ise bu k noktanın asıl veri kümesindeki indekslarini vermektedir. Pekiyi biz bu yöntemde her noktanın en yakın k komşusuna uzaklıkları elde ettikten sonra nasıl bir ölçüt kullanarak noktaların anomali oluşturup oluşturmadığına karar verebiliriz? İlk akla gelen yöntem noktalara en yakın k komşusunun uzaklıklarının ortalamalarını hesaplamak sonra bu ortalamaları standart normal dağılıma uydurup tek taraftan kesim uygulamaktır. #---------------------------------------------------------------------------------------------------------------------------- #---------------------------------------------------------------------------------------------------------------------------- 99. Ders - 02/02/2025 - Pazar #---------------------------------------------------------------------------------------------------------------------------- #---------------------------------------------------------------------------------------------------------------------------- Şimdi k-NN yöntemiyle "zambak (iris)" veri kümesinde anomali tespiti yapan bir örnek üzerinde duralım. Veri kümesinin dataset dizisi biçiminde okunduğunu varsayalım. Her noktanın en yakın NNEIGHBORS kadar komşusunu aşağıdaki gibi elde edebiliriz: nn = NearestNeighbors(n_neighbors=NNEIGHBORS) nn.fit(dataset) distances, indices = nn.kneighbors(dataset) Tabii bu komuşuluğa noktanın kendisi de dahil edilmektedir. Her noktanın kendisine uzaklığı 0 olacaktır. Yani elde edilen distances matrislerinin ilk elemanları 0 olacaktır. Hesaplamalarda bu 0 olan sütun dilimlemeyle tamamen atılabilir. Şimdi bu her noktaya en yakın k tane noktanın ortalamalarını hesaplayabiliriz: mean_distances = distances[:, 1:].mean(axis=1) Burada 0 olan sütunun atıldığına dikkat ediniz. Tabii biz bu sütunu atmasak da aslında değişen bir şey olmayacaktır. Eşik değeri belirlemek için bu ortalamaları normal dağılıma uydurabiliriz. Böylece eşik olarak ortalamadan belli bir standart sapmayı alabiliriz: mean_mean_distances = np.mean(mean_distances) std_mean_distances = np.std(mean_distances, ddof=0) threshold = mean_mean_distances + THRESHOLD_STD * std_mean_distances Burada THRESHOLD_STD ortalamadan kaç standart sapma uzak olanların anomali olarak değerlendirileceğini belirtmektedir. Bu değerin tamsayı olması gerekmemektedir. Anımsanacağı gibi ortalamadan iki standart sapma uzaklık çift taraflı %95 civarında 3 standart sapma uzaklık %99 civarında alan kaplamaktadır. Tabii biz burada iki taraflı değil tek taraflı bir uzaklık uygulamaktayız. Bu işlemlerden sonra artık normal noktalar ve anomali noktaları aşağıdaki gibi elde edilebilir: normal_points = dataset[mean_distances <= threshold] anomaly_points = dataset[mean_distances > threshold] normal_indices = np.where(mean_distances <= threshold) anomaly_indices = np.where(mean_distances > threshold) Aşağıda kodun tamamı verilmiştir. #---------------------------------------------------------------------------------------------------------------------------- NNEIGHBORS = 5 THRESHOLD_STD = 2 import numpy as np import pandas as pd df = pd.read_csv('iris.csv') dataset = df[['SepalLengthCm','SepalWidthCm','PetalLengthCm','PetalWidthCm']].to_numpy('float32') from sklearn.preprocessing import StandardScaler ss = StandardScaler() ss.fit(dataset) scaled_dataset = ss.transform(dataset) from sklearn.neighbors import NearestNeighbors nn = NearestNeighbors(n_neighbors=NNEIGHBORS) nn.fit(dataset) distances, indices = nn.kneighbors(dataset) mean_distances = distances[:, 1:].mean(axis=1) mean_mean_distances = np.mean(mean_distances) std_mean_distances = np.std(mean_distances, ddof=0) threshold = mean_mean_distances + THRESHOLD_STD * std_mean_distances normal_points = dataset[mean_distances <= threshold] anomaly_points = dataset[mean_distances > threshold] normal_indices = np.where(mean_distances <= threshold) anomaly_indices = np.where(mean_distances > threshold) print(f'Anomaly points:\n{anomaly_points}') print(f'Anomaly indices: {anomaly_indices}') from sklearn.decomposition import PCA pca = PCA(n_components=2) reduced_dataset = pca.fit_transform(dataset) import matplotlib.pyplot as plt plt.figure(figsize=(10, 8)) plt.title('Clustered Points') plt.title('k-NN Anomaly Detection', fontsize=12) plt.scatter(reduced_dataset[normal_indices, 0], reduced_dataset[normal_indices, 1]) plt.scatter(reduced_dataset[anomaly_indices, 0], reduced_dataset[anomaly_indices, 1], marker='x', color='red') 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 anomali içermektedir. Yani veri kümesi denetimli (supervised) bir biçimde oluşturulmuştur. 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. #---------------------------------------------------------------------------------------------------------------------------- EPS = 10 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=EPS, min_samples=5) dbs.fit(dataset) from sklearn.decomposition import PCA pca = PCA(n_components=2) reduced_dataset = pca.fit_transform(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(reduced_dataset[dbs.labels_ == i, 0], reduced_dataset[dbs.labels_ == i, 1]) plt.scatter(reduced_dataset[dbs.labels_ == -1, 0], reduced_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 da 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 uzaklıklar 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 gidilebilir. Ancak açıklanan varyans oranının %70'lerin aşağısına düşürülemsi uygun olabilmektedir. Bu işlemi "zambak (iris)" veri kümesi üzerinde gerçekleştirelim. Veri kümesinin dataset biçiminde bir NumPy dizisine okunduğunu varsayıyoruz. Bunun için önce PCA işlemi ile veri kümesi üzerinde özellik indirgemesi yaparız. Örneğin zambak veri kümesi için 4 özelliği 2 özelliğe indirgeyelim: pca = PCA(n_components=2) reduced_dataset = pca.fit_transform(dataset) Anımsanacağı gibi bu tür sınıfların inverse_transform isimli metotları da vardı. PCA sınıfının inverse_transform metodu veri kümesini ters dönüşüm yaparak orijinal boyuta yükseltmektedir: inversed_dataset = pca.inverse_transform(reduced_dataset) Biz bu iki veri kümesi arasındaki farka bakarak bu farkın yüksek olduğu noktaları anomali noktaları biçiminde belirleyeceğiz. Pekiyi buradaki ölçütümüz ne olabilir? Aslında burada çeşitli ölçütler kullanılabilir. Örneğin iki nokta arasında Öklit uzaklığını ölçüt olarak kullanabiliriz. Tabii karekök alma işlemine gerek yoktur. def anomaly_scores(original_data, manipulated_data): return np.sum((original_data - manipulated_data) ** 2, axis=1) Artık her nokta için anomali skorları tespit edilebilir: scores = anomaly_scores(dataset, inversed_dataset) Burada anomaly değerlerinin belli bir yüzdesi elde edilebilir. Bunun için NumPy'ın quantile fonksiyonunu kullanalım. Bu fonksiyon veri kümesi içerisindeki değerlerin belli bir yüzdelik kısmını belirten değeri vermektedir: q = np.quantile(scores, 1 - ANOMALY_RATIO) anomalies = dataset[scores > q] normals = dataset[scores <= q] Görüldüğü gibi veri kümesinde elde skorlar arasında en kötü olan belli bir yüzdesi elde edilmiştir. Tabii bu işlem quantile fonksiyonu yerine dizinin sıraya dizilmesi ve belli bir yüzdelik kısmının elde edilmesi biçiminde manuel olarak da yapılabilirdi. Aşağıdaki örnekte zambak veri kümesindeki 4 sütundan 2 sütuna 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 olarak tespit edilmiştir. #---------------------------------------------------------------------------------------------------------------------------- REDUCTION_FEATURES = 2 ANOMALY_RATIO = 0.05 import numpy as np import pandas as pd df = pd.read_csv('iris.csv') dataset = df[['SepalLengthCm','SepalWidthCm','PetalLengthCm','PetalWidthCm']].to_numpy('float32') from sklearn.preprocessing import StandardScaler ss = StandardScaler() scaled_dataset = ss.fit_transform(dataset) from sklearn.decomposition import PCA pca = PCA(n_components=REDUCTION_FEATURES) reduced_dataset = pca.fit_transform(scaled_dataset) inversed_dataset = pca.inverse_transform(reduced_dataset) def anomaly_scores(original_data, manipulated_data): return np.sum((original_data - manipulated_data) ** 2, axis=1) scores = anomaly_scores(dataset, inversed_dataset) q = np.quantile(scores, 1 - ANOMALY_RATIO) anomalies = dataset[scores > q] normals = dataset[scores <= q] pca = PCA(n_components=2) reduced_anomalies = dataset[scores > q] reduced_normals = dataset[scores <= q] import matplotlib.pyplot as plt plt.title('Anomalies') plt.legend(['Normal points', 'Anomalies']) figure = plt.gcf() figure.set_size_inches((10, 8)) plt.scatter(reduced_normals[:, 0], reduced_normals[:, 1], color='blue') plt.scatter(reduced_anomalies[:, 0], reduced_anomalies[:, 1], marker='x', color='red') plt.show() print(f'Number of anomaly pointes: {len(anomalies)}') #---------------------------------------------------------------------------------------------------------------------------- Aşağıdaki örnekte kredi kartı işlemleri üzerinde özellik indgirmesi ve yükseltmesi yöntemi ile anomali tespiti yapılmıştır. #---------------------------------------------------------------------------------------------------------------------------- REDUCTION_FEATURES = 10 ANOMALY_RATIO = 0.0001 import numpy as np 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=REDUCTION_FEATURES) reduced_dataset = pca.fit_transform(dataset) inversed_dataset = pca.inverse_transform(reduced_dataset) def anomaly_scores(original_data, manipulated_data): return np.sum((original_data - manipulated_data) ** 2, axis=1) scores = anomaly_scores(dataset, inversed_dataset) q = np.quantile(scores, 1 - ANOMALY_RATIO) anomalies = dataset[scores > q] normals = dataset[scores <= q] pca = PCA(n_components=2) reduced_anomalies = dataset[scores > q] reduced_normals = dataset[scores <= q] import matplotlib.pyplot as plt plt.title('Anomalies') plt.legend(['Normal points', 'Anomalies']) figure = plt.gcf() figure.set_size_inches((10, 8)) plt.scatter(reduced_normals[:, 0], reduced_normals[:, 1], color='blue') plt.scatter(reduced_anomalies[:, 0], reduced_anomalies[:, 1], marker='x', color='red') plt.show() print(f'Number of anomaly pointes: {len(anomalies)}') #---------------------------------------------------------------------------------------------------------------------------- İstatistiksel sınıflandırma yöntemlerinin en yalınlarından biri "Naive Bayes" denilen yöntemdir. Burada "naive" sıfatı yöntemde kullanılan bazı safça 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 "denetimli (supervised)" bir yöntemdir. Yöntemin temeli ünlü olasılık kuramcısı Thomas Bayes'in "Bayes Kuralı (Bayes Rule)" olarak bilinen teoremine dayanmaktadır. İstatistikte Bayes Kuralına "koşulu olasılık (conditional probablity)" kuralı da denilmektedir. İstatistikte koşullu 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 genellikle P(A|B) biçiminde gösterilir. Burada P(A|B) ifadesi "B olayı olmuşken A olayı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. Koşullu olasılık bir aksiyon gibi kabul edilebilir. Ayrıca bir ispatı yapılamamaktadır. Ancak Kolmogorov'un temel aksiyomlarına da uymaktadır. Koşuluu olasılık ifadesine bir kez daha bakınız: P(A|B) = P(A, B) / P(B) Bu eşitlikten P(A, B) olasılığını elde edebiliriz: P(A, B) = P(A|B) / P(B) Şimdi de bunun tersi olan 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) Kesişim işlemi değişme özelliğine sahip olduğuna göre P(A, B) olasılığı iki biçimde de yazılabilmektedir: P(A, B) = P(A|B) / B P(A, B) = P(B|A) / 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 örnek oluşturan 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) Koşullu olasılıkla ilgili diğer bir soru çeşiti de "ayrık bütüne tamamlayan olaylarla" ilgilidir. Soruyu soran kişi ayrık bütüne tamamlayan olayları belirtir. Sonra bunların bazılarının içinde bulunduğu bir olayın olsılığını sorar. Örneğin bütüne tamamlayan olaylar A, B, C, D, E olsun. Burada bir X olayının söz konuşu olduğunu düşünelim. P(X) = P(X|A) + P(X|B) + P(X|C) + P(X|D) + P(X|E) Burada koşulu olasılıkları da aşağıdaki gibi tersten açabiliriz: P(X|A) = (A|X) * P(X) / P(A) + (B|X) * P(X) / P(B) + (C|X) * P(X) / P(C) biçiminde yazılabilir. Örnek bir soru şöyle olabilir (KTÜ Olasılık ders notlarındna alınmış bir örnek): "Pense üretim firması, üç ayrı fabrikasında aynı penseleri üretmektedir. 1 no’lu fabrikada,hem 2 hemde 3 no’lu fabrikalardaki üretimlerin iki katı kadarpense üretilmektedir. Hem 1 hem de 2 no’lu fabrikalardaki üretimin %2’si, 3 no’lu fabrikadaki üretimin %4’ü kusurlu çıkmaktadır. Üretilen penselerin tümü aynı kasaya konularak bu kasadan rastgele bir penseçekiliyor. Bu pensenin kusurlu olma olasılığı nedir?" Bu sorada P(kusurlu_pense) olasılığı sorulmaktadır. Bu olasılık aşağıdaki biçimde ifade edilebilir: P(kusurlu_pense) = P(kusurlu_pense|A) + P(kusurlu_pense|B) + P(kusurlu_pense|C) #---------------------------------------------------------------------------------------------------------------------------- 100. Ders - 08/02/2025 - Cumartesi #---------------------------------------------------------------------------------------------------------------------------- #---------------------------------------------------------------------------------------------------------------------------- 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ı etkilememektedir. Bir olay oluşsa da olmamışsa da diğer olayın oluşunu etkilemiyorsa bu iki olay istatistiksel bakımdan bağımsızıdır. P(A|B) koşullu olasılığında B'nin olması ile A'nın olmasının hiçbir ilgisi 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 sonra 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ü birbirine etki etmektedir. Ö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. Konunun "kaos teorisi" bağlamında felsefi açılımları da vardı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ğerlerden 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|x1, x2, x3, ..., xn) olasılıkları hesaplanıp bunların hangisi yüksekse o kategori kestirim için seçilebilir. P(Yk|x1, 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 faydalı 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 paydadaki ifade 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) * P(Yk) Burada 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. Çünkü çıkardığımız formüldeki olasılıkları hesaplayabilmek için sütunların kategorik olması gerekir. 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ı kategorik bazıları nümerik değerlerden oluşur. Pekiyi bu durumda ne yapılacaktır? Burada iki yönteme başvurulabimektedir: 1) Değerleri ayrık hale getirmek (discretazation) 2) Değerleri belli bir dağılma (tipik olarak normal dağılıma) uydurmak. Değerlerin ayrık hale getirilmesi farklıo değerlerin aynıymış gibi işlem görmesine yol açmaktadır. Bu nedenle genellikle değerlerin normal dağılıma uydurulması yöntemi izlenmektedir. Bu yönteme "Gaussian Naive Bayes (Gaussian NB)" denilmektedir. Bu yöntemde sütunların her Y kategorisi için ortalamaları ve standart sapmaları hesaplanır. Bu değerlerden hareketle Gauss foksiyonu (normal dağılım fonksiyonu) oluşturulur ve olasılıklar bu Gauss fonksiyonundan hareketle elde edilir. Örneğin A, B, C biçiminde 3 nümarik sütunu olan C1 ve C2 sınıflarına sahip veri kümesinde Guassian NB yöntemini uygulamak isteyelim. Bizim aşağıdaki Gauss fonksiyonlarını oluşturmamız gerekir. C1 sınıfına ilişkin A sütun değerleri için Gauss Fonksiyonu C1 sınıfına ilişkin B sütun değerleri için Gauss Fonksiyonu C1 sınıfına ilişkin C sütun değerleri için Gauss Fonksiyonu C2 sınıfına ilişkin A sütun değerleri için Gauss Fonksiyonu C2 sınıfına ilişkin B sütun değerleri için Gauss Fonksiyonu C2 sınıfına ilişkin C sütun değerleri için Gauss Fonksiyonu Tabii aslında fonksiyon oluşturmaya gerek yoktur. Zaten bu fonksiyonların değerleri ortalama ve standart sapma parametreleriyle elde edilebilmektedir. O halde Navive Bayes yöntemiyle örneğin verilen bir Xa, Xb, Xc değerinin hangi sınıfa ilişkin olduğu aşağıdaki iki olasılığın karşılaştırılması ile tespit edilebilir: P(C1|Xa, Xb, Xc) = P(Xa|C1) * P(Xb|C1) * P(Xc|C1) * P(C1) P(C2|Xa, Xb, Xc) = P(Xa|C2) * P(Xb|C2) * P(Xc|C2) * P(C2) İşte Gaussian NB yönteminde örneğin P(Xa|C1) olasılığı C1 sınıfına ilişkin A sütun değerleri için oluşturulan Gauss eğrisinde A değerine karşılık gelen değer olarak 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ı eğer sütunlar sıklık sayılarından oluyorsa olasılıkların Binom dağılımından hareketle hesaplanması daha iyi sonuçların elde edilmesine yol açmaktadı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ı. Yani örneğin biz #---------------------------------------------------------------------------------------------------------------------------- #---------------------------------------------------------------------------------------------------------------------------- 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: Bütün sütunların nümerik olduğu durumlarda kullanılmaktadır. BernoullyNB: Sütunların yalnızca 1 ve 0'lardan oluştuğu durumlarda kullanılmaktadır. Örneğin CountVectorizer sınıfı ile yazıların vektörize edilmesi sonucunda elde edilen veri kümelerinde kullanılabilir. MultinomialNB: Sütunların sıklık sayılarından oluştuğu durumlarda kullanılmaktadır. Örneğin CountVectorizer sınıfı ile yazıların sıklık sayılarıyla vektörüze edilmesi sonucunda elde edilen veri kümelerinde kullanılabilir. 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. Pekiyi veri kümesindeki bazı sütunlar kategorik bazı sütunlar nümerik ise Naive Bayes uygulanabilir mi? scikit-learn içerisinde doğrudan bu biçimdeki veri kümeleri üzerinde çalışan bir sınıf yoktur. Bu durumda ilk akla gelen yöntem nümerik sütunlarla kategorik sütunları ayırmak nümerik sütunlara Gaussian NB, kategorik sütunlara Categorical NB uygulamak olabilir. Tabii buradan iki ayrı sonuç elde edilecektir. Bu iki sonucun (iki sınıfın) tek bir sonuca (sınıfa) indirgenmesi gerekir. Bunun işlem tipik olarak nümerik sütunlar için elde edilen olasılıkla (sınıf değil) kategorik sütunlar için elde edilen olasılığın belli ağırlıklarla ilişkilendirilmesi olabilir. Örneğin nümerik sütunlardan elde edilen C1 olasılığı 0.4, C2 olasılığı 0.6 olsun. Kategorik sütunlardan elde edilen C1 olasılığı 0.7, C2 olasılığı 0.3 olsun. Şimdi biz bunların belli ağırlıklarla ağırlıklı ortalamasını hesaplayıp nihani soncu elde edebiliriz. Örneğin nümerik sütunların ağırlığı 0.4 kategrik sütunların ağırlığı 0.6 olsun. C1 olsalığı = (0.4 * 0.4 + 0.7 * 0.6 ) / 2 = 0.29 C2 olsalığı = (0.6 * 0.4 + 0.3 * 0.6 ) / 2 = 0.21 Buradan C1 sınıfı elde edilmektedir. Diğer bir yöntem de "oylama (voting)" yöntemi olabilir. Burada nümerik sütunlarla kategorik sütunlar için ayrı kestirimlerde bulunulur. Eğer iki yöntemde de aynı sınıf bulunursa o sınıf alınır, iki yöntemde farklı sınıflar bulunursa bunlardan biri isteğe bağlı olarak tercih edilebilir. Tabi iaslında sütunların bazıları kategorik bazıları nümerik bazıları 0 ve 1'lerden oluşan bazıları da sıklık sayılarından oluşabilir. Bu durumda oylama daha anlamlı uygulanabilir. #---------------------------------------------------------------------------------------------------------------------------- #---------------------------------------------------------------------------------------------------------------------------- 101. Ders - 09/02/2025 - Pazar #---------------------------------------------------------------------------------------------------------------------------- #---------------------------------------------------------------------------------------------------------------------------- Şimdi CatericalNB sınıfının kullanımına bir örnek verelim. Bunun için "Araba Değerlendirme (Car Evaluation)" veri kümesini kullanalım. Bu veri kümesini aşağıdaki bağlantıdan indirebilirsiniz: https://archive.ics.uci.edu/dataset/19/car+evaluation Veri kümesindeki sütunlar bir arabanın çeşitli özelliklerini kategorik olarak betimlemektedir. Veri kümesinde toplam 4 sınıf vardır: unacc acc good v-good Veri kümesi 6 sütuna sahiptir. Sütunların hepsi kategorik değerlerden oluşmaktadır. Sütunları oluşturan kategorik değerler şöyledir: buying v-high, high, med, low maint v-high, high, med, low doors 2, 3, 4, 5-more persons 2, 4, more lug_boot small, med, big safety low, med, high Veri kümesi "car.data" dosyasının içerisindedir ancak CSV formatındadır. Dosyada bir başlık kısmı yoktur. Görünümü aşağıdaki gibidir: vhigh,vhigh,2,2,small,low,unacc vhigh,vhigh,2,2,small,med,unacc vhigh,vhigh,2,2,small,high,unacc vhigh,vhigh,2,2,med,low,unacc vhigh,vhigh,2,2,med,med,unacc vhigh,vhigh,2,2,med,high,unacc vhigh,vhigh,2,2,big,low,unacc vhigh,vhigh,2,2,big,med,unacc vhigh,vhigh,2,2,big,high,unacc vhigh,vhigh,2,4,small,low,unacc vhigh,vhigh,2,4,small,med,unacc vhigh,vhigh,2,4,small,high,unacc vhigh,vhigh,2,4,med,low,unacc ..... Dosyayı aşağıdaki gibi okuyabiliriz: df = pd.read_csv('car.data', header=None) Burada dosyanın başlık kısmı olmadığı için header=None geçildiğine dikkat ediniz. DataFrame nesnesi içerisinde sütun isimlerini aşağıdaki gibi oluşturabiliriz: df.columns = ['buying', 'maint', 'doors', 'persons', 'lug_boot', 'safety', 'class'] Bizim önce sütunlardaki etiketleri yazısal olmaktan çıkartıp sayısal hale getirmemiz gerekir. Anımsanacağı gibi kategorik verileri sayısal hale çevirmek için LabelEncode ya da OrdinalEncoder sınıfları kullanılabiliyordu. Bu iki sınıf arasındaki fark LabelEncoder sınıfının tek hamlede tek bir sütun üzerinde işlem yapması OrdinalEncoder sınıfının ise tek hamlede birden fazla sütun üzerinde işlem yapabilmesidir: oe = OrdinalEncoder() le = LabelEncoder() encoded_dataset_x = oe.fit_transform(df.iloc[:, :-1].to_numpy(dtype='str')) encoded_dataset_y = le.fit_transform(df.iloc[:, -1].to_numpy(dtype='str')) Şimdi de veri kümesini eğitim ve test biçiminde ikiye ayırabiliriz: training_dataset_x, test_dataset_x, training_dataset_y, test_dataset_y = \ train_test_split(encoded_dataset_x, encoded_dataset_y, test_size=0.2) Artık CategoricalNB sınıfı ile Navive Bayes sınıflandırması yapabiliriz. Sınııfn __init__ metodunun parametrik yapısı şöyledir: class sklearn.naive_bayes.CategoricalNB(*, alpha=1.0, force_alpha=True, fit_prior=True, class_prior=None, min_categories=None)[source] Buradaki parametreler algoritmanın içsel işleyişi ile ilgilidir. Nesne default değerler ile yaratılabilir: cnb = CategoricalNB() Bundan sonra fit işlemi yapılır: cnb.fit(training_dataset_x, training_dataset_y) fit işlemiyle tüm sınıflandırma bilgileri elde edilir. Sınıfın transform isminde bir metodu yoktur. Doğrudan predict işlemi ile kestirim yapılabilir. Örneğin biz sınıflandırma işleminin başarısını test veri kümesi ile şöyle belirleyebiliriz: test_result = cnb.predict(test_dataset_x) score = np.sum(test_dataset_y == test_result) / len(test_dataset_y) print(score) Anımsanacağı gibi karşılıklı elemanların eşitliğine dayalı oran aynı zamanda skleran.metrics modülündeki sccuracy_score fonksiyonuyla da elde edilebiliyordu: score = accuracy_score(test_dataset_y, test_result) Kestirim işlemi de aşağıdaki gibi yapıalbilir. Kestirilecek değerlerin "predict.csv" isimli dosyada aşağıdaki gibi bulunduğunu varsayalım: predict_dataset_x = pd.read_csv('predict.csv', header=None).to_numpy(dtype='str') encoded_predict_dataset_x = oe.transform(predict_dataset_x) predict_result = cnb.predict(encoded_predict_dataset_x) print(predict_result) predict_result_names = le.inverse_transform(predict_result).tolist() Kod bir bütün olarak aşağıda verilmiştir. #---------------------------------------------------------------------------------------------------------------------------- import pandas as pd df = pd.read_csv('car.data', header=None) df.columns = ['buying', 'maint', 'doors', 'persons', 'lug_boot', 'safety', 'class'] from sklearn.preprocessing import OrdinalEncoder, LabelEncoder oe = OrdinalEncoder() le = LabelEncoder() encoded_dataset_x = oe.fit_transform(df.iloc[:, :-1].to_numpy(dtype='str')) encoded_dataset_y = le.fit_transform(df.iloc[:, -1].to_numpy(dtype='str')) from sklearn.model_selection import train_test_split training_dataset_x, test_dataset_x, training_dataset_y, test_dataset_y = \ train_test_split(encoded_dataset_x, encoded_dataset_y, test_size=0.2) from sklearn.naive_bayes import CategoricalNB cnb = CategoricalNB() cnb.fit(training_dataset_x, training_dataset_y) test_result = cnb.predict(test_dataset_x) from sklearn.metrics import accuracy_score score = accuracy_score(test_dataset_y, test_result) print(score) """ import numpy as np score = np.sum(test_dataset_y == test_result) / len(test_dataset_y) print(score) """ predict_dataset_x = pd.read_csv('predict.csv', header=None).to_numpy(dtype='str') encoded_predict_dataset_x = oe.transform(predict_dataset_x) predict_result = cnb.predict(encoded_predict_dataset_x) print(predict_result) predict_result_names = le.inverse_transform(predict_result).tolist() #---------------------------------------------------------------------------------------------------------------------------- Şimdi de nümerik sütunlardan oluşan bir veri kümesi üzerinde Gaussian NB yöntemini uygulayalım. Bunun için "Breast Cancer Wisconsin" veri kümesini kullanacağız. "Breast Cancer Winconsin" 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 bağlantıdan 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. Gaussian NB yöntemini uygulamak için sklearn.naive_bayes modülündeki GaussianNB sınıfı kullanılmaktadır. Sınıfın __init__ metodunun parametrik yapısı şöyledir: class sklearn.naive_bayes.GaussianNB(*, priors=None, var_smoothing=1e-09) Nesneyi default argümanlarla yaratabiliriz: gnb = GaussianNB() Veri kümesi üzerinde özellik ölçeklemesi yapılmasına gerek olmadığını belirtmiştik. fit işlemi aşağıdaki gibi uygulanabilir: Yine sınıfın predict metodu ile kestirim yapılabilir: test_result = gnb.predict(test_dataset_x) test_dataset_y verileri ile kesitirimdeki verileri sınıflandırmanın başarısını test etmek için kullanabiliriz: accuracy = accuracy_score(test_dataset_y, test_result) print(accuracy) Kod bir bütün olarak aşağıda verilmiştir. #---------------------------------------------------------------------------------------------------------------------------- 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() """ 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) test_result = gnb.predict(test_dataset_x) from sklearn.metrics import accuracy_score accuracy = accuracy_score(test_dataset_y, test_result) print(accuracy) #---------------------------------------------------------------------------------------------------------------------------- Naive Bayes yönteminde artırımlı eğitim işlemleri algoritmayı sıfıran yeniden çalıştırmadan 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. scikit-learn içerisindeki Naive Bayes sınıflarının partial_fit metotları bukunmaktadır. #---------------------------------------------------------------------------------------------------------------------------- #---------------------------------------------------------------------------------------------------------------------------- Naive Bayes yönteminin şimdiye kadar gördüğümüz ve henüz görmediğimiz yöntemlere göre avantajları ve dezavantajları şöyle ifade edilebilir: - Tüm sütunları kategorik olan 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 satıra sahip veri kümelerinde 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 nispeten 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. (Yani biz bir kez değil veri geldikçe partial_fit ile eğitime devam edebiliriz.) - Naive Bayes yöntemi sütunların istatistiksel bakımdan bağımısz olduğu varsayımına dayanmaktadır. Uygulamada bu koşulun sağlanması çoğu kez mümkün olamamaktadır. Bu durumda modelin başarısı düşecektir. Örneğin bir sütunda hava durumu (normal, güneşli, yağışlı gibi), diğer sütunda havadaki nem oranı bulunuyor olsun. Bu iki sütun aslında birbirinden bağımsız değildir. Yöntem bunların bağımsız olduğu fikriyle uygulanmaktadır. Bu da başarının düşmesine yol açmaktadır. Sütunlar birbirlerine ne kadar bağımlıysa yöntemin etkinliği de buna bağlı olarak düşmektedir. - Veri kümesinde hiç bulunmayan bir sütun etiketi ile tahmin yapılmaya çalışılırsa 0 değeri elde edilmektedir. Bunun nedeni Naive Bayes formülünden anlaşılabilir: argmax(k = 1, ..., m) = P(x1|Yk) * P(x2|Yk) * P(x3|Yk) * ... * P(xn|Yk) * P(Yk) Burada bir sütun ile 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 nümerik ve karma sütunlu veri kğmelerinde alternatif yöntemlere göre daha düşük performans gösterme eğilimindedir. Tabii hangi durumlarda hangi yöntemin daha iyi performans göstereceği deneme yanılma yöntemine başvurulmadan çoğu kez önceden belirlenememektedir. Bu durumda uygulamacı birden fazla yöntemi deneyerek kendi veri kümesi için en iyi performansı gösteren yöntemi kullanabilir. Ya da başka bölümde ele alacak olduğumuz "topluluk yöntemlerinden (ensemble methods) faydalanabilir. #---------------------------------------------------------------------------------------------------------------------------- #---------------------------------------------------------------------------------------------------------------------------- Bu bölümde istatistiksel doğrusal regresyon, polinomsal regresyon ve doğrusal sınıflandırıcılardan (linear classifiers) lojistik regresyon üzerinde duracağız. #---------------------------------------------------------------------------------------------------------------------------- #---------------------------------------------------------------------------------------------------------------------------- Girdi ile çıktı arasında ilişki kurma sürecine istatistikte "regresyon" denilmektedir. Girdiyle çıktı arasındaki ilişki bir fonksiyonla ifade edilebildiğine göre regresyon sürecini de biz aslında girdi ile çıktı arasındaki ilişkiyi belirten uygun bir fonksiyonun bulunması süreci olarak ele alabiliriz. Matematiksel biçimde açıklarsak regresyon aslında y = f(x) biçiminde bir f fonksiyonun bulunması sürecidir. Burada x ve y birden fazla değişkeni temsil ediyor olabilir . Yani x bir tane değil x0, x1, ... xn biçiminde n tane olabileceği gibi y de bir tane değil yo, y1, ... ym biçiminde m tane olabilir.) x değerlerinin bir den fazla olduğu fonksiyonlara matematikte "çok değişkenli fonksiyonlar" denildiğini anımsayınız. Pekiyi girdi ile çıktı arasında ilişki kurmanın amacı ne olabilir? Şüphesiz en önemli amaç kestirimde bulunmaktır. Örneğin elimizde birtakım geçmiş veriler vardır. Biz de gelecekteki durumun ne olabileceğiyle ilgili karar vermek isteyebiliriz. Bu durumda gelecekteki verileri regresyon sonucunda bulduğumuz f fonksiyonuna girdi yaparak sonucu elde ederiz. Regresyon modelleri ve problemleri istatistikte çeşitli biçimlerde sınıflandırılabilmektedir. Maalesef herkesin hemfikir olduğu bir sınıflandırma biçimi yoktur. Biz burada istatistiksel regresyon modellerini tipik olarak aşağıdaki gibi sınıflandıracağız: Doğrusal Regresyon (Linear Regression): Girdi ile çıktı arasındaki ilişkiyi parametrelerin doğrusal bileşimleriyle ifade eden regresyon modelidir. Yani doğrusal regresyonla girdi ile çıktı arasında ilişkiyi belirten doğrusal bir fonksiyon bulunmaya çalışılır. Doğrusal regresyonda girdi (yani bağımsız değişen) bir tane ve çıktı da (bağımlı değişken) bir tane ise buna "basit doğrusal regresyon" denilmektedir. Tabii gerçek yaşama ilişkin problemlerde genellikle girdi bir tane olmaz. Örneğin bizim kullandığımız veri kümelerindeki özellikler (sütunlar) girdileri oluştururlar. Böylece regresyon modelinde çok sayıda girdi söz konusu olur. İşte eğer doğrusal regresyonda girdiler (yani bağımsız değişkenler) birden fazla ise buna "çoklu doğrusal regresyon (multiple linear regression)" denilmektedir. Doğrusallığın değişken sayısı ile ilgili olmadığına değişkenlerin dereceleri ile ilgili olduğuna dikkat ediniz. Örneğin n tane değişken içeren doğrusal bir fonksiyonun genel biçimi şöyledir: f(x) = a1x1 + a2x2 + a3x3 + ... + anxn + b Biz bu doğrusal fonksiyonu vektörel biçimde şöyle de ifade edebiliriz: f(X) = AX + b Burada A bir satır vektörünü X ise bir sütun vektörünü belirtmektedir. Aslında AX işleminin bir "dot product" oluşturduğuna dikkat ediniz. Polinomsal Regresyon (Polynomial Regression): Verilerin grafiğini çizdiğimizde ilişkinin doğrusal olup olmadığı hemen gözle görülebilmektedir. Bağımsız değişkenlerin herhangi birinin üssü 1'den büyükse bu tür regresyon modellerine polinomsal regresyon modelleri denilmektedir. Başka bir deyişle polinomsal regresyon aşağıdaki gibi bir polinomsal fonksiyonun bulunması sürecidir: f(x)= a0x^0 + a1x^1 + a2x^2 + ... + anx^n Aslında polinomsal regresyon eğer katsayılarla değişkenler yer değiştirirse doğrusal regresyona benzemektedir: f(a)= x^0a0 + x^1a1 + x^2a2 + ... + x^nan Doğrusal Olmayan Regresyon (Nonlinear Regression): Her ne kadar polinomlar doğrusal fonksiyonlar değilse de katsayılarla değişkenler yer değiştirdiğinde doğrusal gibi ele alınabilmektedir. İstatistikte doğrusal olmayan regresyon denildiğinde katsayılarla değişkenler değiştirildiğinde yine doğrusal olmayan fonksiyonların elde edildiği regresyonlar anlaşılmaktadır. Bu tür fonksiyonalar genel olarak üstel ifadeler, logaritmik ifadeler, pay ve paydasında bağımsız değişkenlerin bulunduğu oransal ifadeler içermektedir. Lojistik Regresyon (Logistic/Logit Regression): Bağımlı değişkenin sürekli bir değer almadığı, kategorik değer aldığı durumlarda uygulanan regresyonlara lojistik regresyon ya da logit regresyonu denilmektedir. Lojistik regresyon terimi tipik olarak çıktının 0 ya da 1 gibi ikili değerlere sahip olduğu durumlar için kullanılmaktadır. (Örneğin çıktı "evli mi bekar mı", "hasta mı sağlıklı mı", "film iyi mi kötü mü" gibi iki seçenekten biri olabilmektedir.) Ancak zamanla bu terim genişletilmiştir. Çıktının ikiden fazla kategoriye ayrıldığı durumlar için de "çok sınıflı lojistik regresyon (multinomial logistic regression)" terimi kullanılmaktadır. Ayrıca "sıralı lojistik regresyon (ordinal logistic regression)" denilen bir lojistik regresyon modelinde girdi ve çıktılar kategorik değil sıralı ölçeklere ilişkin olabilmektedir. Regresyonlar bağımlı ve bağımsız değişken sayılarına göre de sınıflandırılmaktadır: - Basit Regresyon (Simple Regression): Bağımsız değişken sayısı bir tane ise buöyle regresyonlara basit regresyonlar denilmektedir. - Çoklu Regresyon (multiple regression): Bağımsız değişken sayısının (yani girdilerin) birden fazla olduğu fakat bağımlı değişken sayısının (yani çıktının) bir tane olduğu regresyon modelleri için kullanılan bir terimdir. Örneğin bir otomobilin 8 özelliğinden onun yakıt harcamasının tahmin edilmek istendiği regresyon modeli çoklu regresyon modelidir. Ya da örneğin "diabestes.csv" örneğinde olduğu gibi kişinin 8 biyomedikal bilgisinden hareketle kişinin şeeker hastası olup olmadığının tahmin edilmesi için oluşturulan model de çoklu lojistik regresyon modelidir. - Çok Değişkenli Regresyon (Multivariate Regression): Eğer regresyon modelinde bağımlı değişkenin sayısı birden fazlaysa (yani çıkktı birden fazlaysa) buna da "çok değişkenli (multivariate)" regresyon denilmektedir. Örneğin öğrencinin bazı girdi bilgileri olsun (bağımsız değişkenler) biz de onun sınavda alacağı notu ve ortalama kaç saat uyuduğunu tahmin etmek isteyelim. Burada tahmin etmek istediğimiz şey birden fazladır. Şüphesiz çok değişkenli regresyonlarda çıktılar birbirlerinden bağımsız ve teker teker olarak da ele alınabilirler. O zaman iki farklı çoklu regresyondan söz ederdik. Fakat çok değişkenli regresyonlarda aynı anda birden fazla çıktının değişiminin belirlenmesi hedeflenmektedir. Yani örneğin 15 tane biyomedikal tetkike bakarak biz bir kişinin "diyabetli olup olmadığını", "kalp hastası olup olmadığını", "hipertansiyonunun olup olmadığını" ayrı ayrı çoklu lojistik regresyonla anlamaya çalışabiliriz. Ancak bu üç hastalık birbirlerini de etkiliyor olabilir. O halde bu üç hastalığın birlikte değerlendirilmesi gerekir. İşte bu durum çok değişkenli lojistik regresyon olarak modellenebilir. #---------------------------------------------------------------------------------------------------------------------------- #---------------------------------------------------------------------------------------------------------------------------- En çok uygulanan istatistiksel regresyon modeli "doğrusal regresyon (linear regression)" denilen modeldir. Tek değişkenli doğrusal regresyona "basit doğrusal regresyon (simple linear regression)" denildiğini söylemiştik. Basit doğrusal regresyon yukarıda da belirttiğimiz gibi noktaları temsil eden bir doğru denkleminin elde edilmesi sürecidir. Eğer böyle bir doğru denklemi elde edilebilirse bu durumda kestirim yapılabilir. Kestirim için X değerleri doğru denkleminde yerine konur ve sonuç elde edilir. Pekiyi basit doğursal regresyonda noktaları temsil eden bir doğru denklemi nasıl elde edilecektir? İşte 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? Örneğin elimizde aşağıdaki gibi X değerleri için onlara karşı gelen Y değerleri olsun: x Y 10 12.3 13.2 18.2 14.3 19.8 ... ... Burada bağımısz değişken bir tane olduğu için "basit doğrusal regresyon" söz konusudur. 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. Aslında doğrusallık genel bir kavramdır. Uzayın boyutu ne olursa olsun doğrusallık durumunda yapılan işlemler benzerdir. İki boyutlu uzaydaki doğru üç boyutlu uzayda bir düzlem haline gelmektedir. Her çok boyutlu uzayın kendi düzlemleri vardır. Çok boyutlu uzaydaki o uzaya ilişkin düzlemlere İngilizce "hyperplane" denilmektedir. Şimdi elimizdeki veri kümesinde iki sütun olduğunu varsayalım. Bu durumda regresyon sürecinde üç boyutlu uzayda bir düzlem elde edilmeye çalışılacaktır. Üç oyutlu uzaydaki düzlem denklemi şöyledir: y = B0 + B1x1 + B2x2 Yukarıda da belirttiğimiz gibi her N boyutlu uzayın da bir düzlemi vardır. Buna genel olarak "hyperplane" denilmektedir. Lineer cebirde doğrusallık ile ilgili işlemler 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 açıklamalar genellikle basitliği nedeniyle iki boyutlu uzayda veriliyor olsa da formüller kolaylıkla genelleştirilebilmektedir. N boyutlu uzayda hyperplane denklemi şöyle oluşturulabilir: y = B0 + B1x1 + B2x2 + B3... + Bnxn Genellikle bu tür doğru denklemleri basitlik sağlamak amacıyla matris formunda gösterilmektedir. Burada B katsayılarının bir satır vektörü X değişkenlerinin de bir sütun vektörü olduğunu varsayarsak aynı denklemi şöyle de ifade edebiliriz: y = BX + b #---------------------------------------------------------------------------------------------------------------------------- #---------------------------------------------------------------------------------------------------------------------------- 102. Ders - 15/02/2025 - Cumartesi #---------------------------------------------------------------------------------------------------------------------------- #---------------------------------------------------------------------------------------------------------------------------- Şimdi basit doğrusal regresyonda B0 ve B1 değerlerinin elde edilmesinde kullanılan "en küçük kareler (ordinary least squares)" yönteminden bahsedelim. Basit doğrusal regresyonda noktaları en iyi ortalayan doğru denklemine ilişkin B0 ve B1 değerleri sırasıyla şu aşamalardan geçilerek elde edilebilmektedir: 1) Önce minimize edilecek fonksiyon belirlenir. Minime edilecek fonksiyon noktaların gerçek y değerlerinden doğru denkleminden elde edilen y değerlerinin farklarının karelerinin toplamları biçiminde oluşturulabilir. (Farklar pozitif ve negatif olabileceği için farkların kareleri alınarak farkların her zaman pozitif olması sağlanmaktadır.) Örneğin veri kümesindeki x0 değeri için y0 değerinin karşı geldiğini varsayalım. Bu durumda biz bu x0 değerini elde ettiğimiz doğru denkleminde yerine koysaydık B0 + B1 x0 değerini elde ederdik. O halde buarada yapılan hata (y0 - (B0 + B1 x0)) ** 2 biçiminde ifade edilebilir. Burada tüm x0'lar Xi ile ve tüm y0'lar da Yi ile gösterilirse yapılan toplam hatayı da şöyle ifade edebiliriz: Toplam Hata = sum((Yi - (B0 + B1 Xi)) ** 2) Burada Xi ve Yi n tane gözlem değerinden oluşan vektörlerdir. Buradaki toplam hatanın daha önce kullanmış olduğumuz "mean squared error" loss fonksiyonuna çok benzediğine diikat ediniz. Biz bu değeri gözlem sayısı olan N'e bölsek iki ifade aynı hale gelecektir. 2) Yukarıdaki ifadenin B0 ve B1 için parçalı türevleri alınıp sıfıra eşitlenirse buradan iki denklem elde edilir. 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. Xi ve Yi değerlerini alarak B0 ve B1 değerlerini veren Python fonksiyonu şöyle yazılabilir: 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 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 yukarıdaki fonksiyon "points.csv" dosyasındaki noktalara uygulanmıştır. "points.csv" dosyasının içeriği şöyledir: x,y 2,4 3,5 5,7 7,10 7,8 8,12 9.5,10.5 9,15 10,17 13,18 Örnekte biz hem noktaların saçılma diyagramını hem de regresyon doğrusunu çizdik. #---------------------------------------------------------------------------------------------------------------------------- import numpy as np import pandas as pd df = pd.read_csv('points.csv') dataset_x = df['x'].to_numpy() dataset_y = df['y'].to_numpy() 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 b0, b1 = linear_regression(dataset_x, dataset_y) x = np.linspace(0, 15, 1000) 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) #---------------------------------------------------------------------------------------------------------------------------- Doğrusal regresyondaki B0 ve B1 katsayıları en küçük kareler formülü dışında iteratif bir biçimde "gradient descent" denilen nümerik analiz yöntemiyle de 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 kümelerinin çok fazla sütunu vardır. Dolayısıyla bu alanlarda "çoklu doğrusal regresyon (multiple linear 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)" denildiğini anımsayınız. Yukarıda da belirttiğimiz gibi çoklu doğrusal regresyon aslında N boyutlu uzayda noktaları en iyi ortalayan 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ğinin anlaşılması biraz lineer cebir ve türev bilgisini gerektirmektedir. Aşağıdaki dokümandan bu bilgileri edinebilirsiniz ya da "ChatGPT" gibi büyük dil modellerinden bu bilgileri edinebilirsiniz: https://eli.thegreenplace.net/2014/derivation-of-the-normal-equation-for-linear-regression Parçalı türevlerin 0'a eşitlenmesi ile nihai olarak elde edilen Beta katsayılarının matrisel gösterimi şu biçimdedir: B = (X.T * X)^-1 * X.T * y Burada X veri kümesindeki dataset_x değerlerini (izleyen paragraflarda ayrıntılarını göreceksiniz) y ise dataset_y değerlerini belirtiyor. Eğer veri kümesinde toplam n tane satır ve k tane sütun varsa X matrisi (n, k) boyutunda, y matrisi ise (n, 1) boyutundadır. Gösterimimizdeki X.T ise X matrisinin transpozesini belirtiyor. X.T matrisinin (k, n) boyutunda olduğuna dikkat ediniz. Bu durumda (k, n) boyutlu bir matris (n, k) boyutlu bir matrisle çarpılırsa (k, k) boyutlu bir kare matris elde edilir. Bunun tersi de yine (k, k) boyutlu bir matristir. Sonra (k, k) boyutlu bir matris (k, n) boyutlu bit matrisle çarpılırsa (k, n) boyutunda bir matris elde edilir. Bu da (n, 1) boyutunda bir matrisle çarpılırsa (k, 1) boyutunda bir matris elde edilecektir. Çarpım boyutlarını aşama aşama şöyle de belirtebiliriz: 1) X.T * X işleminde (n, k) boyutlu bir matrisle (k, n) boyutlu matris çarpılıyor ve (k, k) boyutlu matris elde ediliyor. 2) (X.T * X)^-1 işleminde (k, k) boyutlu matrisin tersi alınıyor ve (k, k) boyutlu bir matris elde ediliyor. 3) (X.T * X)^-1 * X.T işleminde (k, k) boyutlu matrisle (k, n) boyutlu matris çarpılıyor ve (k, n) boyutlu matris elde ediliyor. 4) (k, n) boyutlu bir matrisle (n, 1) boyutlu bir matris çarpılıyor ve (k, 1) boyutlu bir matris elde ediliyor. Bu formüldeki X matrisinin birinci sütununun 1'lerden oluşması gerekmektedir. Bu 1 değerleri aslında B0 katsayısını (intercept) belirtiyor. Bu formül kullanılarak Beta katsayalarını elde eden bir fonksiyon şöyle yazılabilir: def multiple_linear_regression(x, y): ones = np.ones((x.shape[0], 1)) x = np.concatenate((ones, x), axis=1) betas = (np.linalg.inv(x.T @ x) @ x.T @ y).reshape(-1) return betas[0], betas[1:] Fonksiyon iki elemanlı bir demete geri dönmektedir. Demetin birinci elemanı B0 değerinden ("intercept"), ikinci elemanı ise X katsayılaından oluşmaktadır. Fonksiyon şöyle çağrılmalıdır: intercept, coeffs = multiple_linear_regression(dataset_x, dataset_y) Aşağıdaki örnekte biz 3 sütunlu "data.csv" isimli bir dosya oluşturup bu dosyadaki değerlerle yukarıda yazmış olduğumuz fonksiyonun test işlemini yaptık. #---------------------------------------------------------------------------------------------------------------------------- import numpy as np import pandas as pd def multiple_linear_regression(x, y): ones = np.ones((x.shape[0], 1)) x = np.concatenate((ones, x), axis=1) betas = (np.linalg.inv(x.T @ x) @ x.T @ y).reshape(-1) return betas[0], betas[1:] df = pd.read_csv('data.csv') dataset_x = df.iloc[:, :-1].to_numpy() dataset_y = df.iloc[:, -1].to_numpy().reshape(-1, 1) intercept, coeffs = multiple_linear_regression(dataset_x, dataset_y) predict_data = np.array([3, 3, -17]) """ total = intercept for i in range(len(coeffs)): total += coeffs[i] * predict_data[i] print(total) """ result = np.dot(coeffs, predict_data) + intercept print(result) #---------------------------------------------------------------------------------------------------------------------------- Tabii çoklu doğrusal regresyon için oluşturulan genel matrisel formül basit doğrusal regresyon için de kullanılabilir. Aşağıdaki örnekte daha önce yapmış olduğumuz "points.csv" verileri ile basit doğrusal regresyon uygulanmıştır. Ancak bu uygulamada çoklu doğrusal regresyon formülü kullanılmıştır. #---------------------------------------------------------------------------------------------------------------------------- import numpy as np import pandas as pd def multiple_linear_regression(x, y): ones = np.ones((x.shape[0], 1)) x = np.concatenate((ones, x), axis=1) betas = (np.linalg.inv(x.T @ x) @ x.T @ y).reshape(-1) return betas[0], betas[1:] df = pd.read_csv('points.csv') dataset_x = df.iloc[:, :-1].to_numpy() dataset_y = df.iloc[:, -1].to_numpy().reshape(-1, 1) intercept, coeffs = multiple_linear_regression(dataset_x, dataset_y) x = np.linspace(0, 15, 1000) y = intercept + coeffs[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 = np.array([3]) result = np.dot(coeffs, predict_data) + intercept print(result) #---------------------------------------------------------------------------------------------------------------------------- Çoklu doğrusal regresyon ile aktivasyon fonksiyonunun "linear" olduğu tek nörondan oluşan "perceptron" modeli birbirine çok benzemektedir. Nörondaki "bias" değeri de aslında doğru denklemindeki "intercept" değeri gibi işlev görmektedir. Perceptron'un n tane girişi olduğunu kabul edersek nöronda oluşacak dot product şöyle olur: w1x1 + w2x2 + w3x3 + ... + wnxn + bias Bu da zaten n boyutlu uzaydaki hyperplane denklemidir. Tabii biz perceptron ile çözüm yaparken aslında arka planda en küçük kareler yöntemini değil "gradient descent" nümerik optimizasyon yöntemini kullanmış olmaktayız. #---------------------------------------------------------------------------------------------------------------------------- #---------------------------------------------------------------------------------------------------------------------------- En küçük kareler yöntemiyle doğrusal regresyon için özellik ölçeklemesi yapmaya gerek yoktur. Çünkü yöntemin matematiksel temelinde ölçekleme gerekmemektedir. Ancak eğer çözüm "gredient descent" gibi nümerik yöntemlerle yapılıyorsa özellik ölçeklemesi yapılmalıdır. Ancak veri kümsinde uç değerlerin olması (outliers) doğrusal regresyonu oldukça kötü etkilemektedir. Bu nedenle doğrusal regresyon işleminden önce veri kümesinin bu uç değerlerden arındırılması gerekir. #---------------------------------------------------------------------------------------------------------------------------- #---------------------------------------------------------------------------------------------------------------------------- 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: lr.fit(dataset_x, dataset_y) fit işleminden sonra nesnenin coef_ özniteliği beta katsayılarını (B1, B2, B3, ..., Bn) intercept_ örnek özniteliği ise B0 değerini verir. Kestirim işlemi de manuel bir biçimde intercept_ ve coef_ özniteikleri kullanılarak yapılabilir. Ancak zaten sınıfta bu işlemi yapan predict metodu da bulunmaktadır. Aşağıdaki örnekte daha önce vermiş olduğumuz "points.csv" dosyası üzerinde LinearRegression sınıfıyla kestirim yapılmıştır. #---------------------------------------------------------------------------------------------------------------------------- import numpy as np import pandas as pd df = pd.read_csv('points.csv') dataset_x = df['x'].to_numpy() dataset_y = df['y'].to_numpy() from sklearn.linear_model import LinearRegression lr = LinearRegression() lr.fit(dataset_x.reshape(-1, 1), dataset_y) x = np.linspace(0, 15, 1000) 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ılmaktadır. 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 [0, 1] aralığında değer belirtmetedir. 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ının 1'den çıkartılmasıyla hesaplanmaktadır: R^2 değerini şöyle ifade edebiliriz: rsquare = 1 - ((y_true - y_pred)** 2).sum() / ((y_true - y_true.mean()) ** 2).sum()) Burada y_true gerçek y değerlerini y_pred ise doğru denkleminden elde edilen y değerlerini temsil etmektedir. Yukarıda da belirttiğimiz gibi R^2 ne kadar büyük olursa doğrunun 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 daha isabetli de 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 alır. X değerleriyle predict işlemi yapıp R^2 değerini hesaplayarak bize verir. Örneğin: rsquare = lr.score(dataset_x.reshape(-1, 1), dataset_y) R^2 için 0.7'nin yukarısı "iyi", 0.9'un yukarısı "çok iyi" kabul edilmektedir. Her ne kadar istatistikte doğrusal regresyonun başarısı için genellikle R^2 değerine önem veriliyorsa da aslında daha önce gördüğümüz diğer metrikler de kullanılabilmektedir. Örneğin gerçek değerlerle doğru denkleminden elde edilen değerler arasında "mean squared error" ya da "mean_absolute_error" metrikleri uygulanabilir. R^2 değerinin bir alternatifi olarak sütun sayılarını da dikkate alan "düzeltilmiş R^2 (adjusted R^2)" ölçütü de sıkça kullanılmaktadır. Düzeltilmiş R^2 değeri şöyle hesaplanmaktadır: Düzeltilmiş R² = 1 - ((1 - R²) * (n - 1)) / (n - p - 1) #---------------------------------------------------------------------------------------------------------------------------- #---------------------------------------------------------------------------------------------------------------------------- 102. Ders - 16/02/2025 - Pazar #---------------------------------------------------------------------------------------------------------------------------- #---------------------------------------------------------------------------------------------------------------------------- Aşağıdaki örnekte "Boston Hausing Prices" veri kümesi üzerinde çoklu doğrusal regresyon ile yapay sinir modeli uygulanmış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 yazdırılmıştır. Bu R^2 değerinin yüksekliği ile "mean absolute error" 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 StandardScaler ss = StandardScaler() ss.fit(training_dataset_x) scaled_training_dataset_x = ss.transform(training_dataset_x) scaled_test_dataset_x = ss.transform(test_dataset_x) from tensorflow.keras import Sequential from tensorflow.keras.layers import Dense model = Sequential(name='Boston-Housing-Price') model.add(Dense(64, activation='relu', input_dim=training_dataset_x.shape[1], name='Hidden-1')) model.add(Dense(64, activation='relu', name='Hidden-2')) model.add(Dense(1, activation='linear', name='Output')) model.summary() model.compile(optimizer='rmsprop', loss='mse', metrics=['mae']) hist = model.fit(scaled_training_dataset_x, training_dataset_y, batch_size=32, epochs=200, validation_split=0.2) import matplotlib.pyplot as plt plt.figure(figsize=(15, 5)) plt.title('Epoch-Loss Graph', fontsize=14, fontweight='bold') plt.xlabel('Epochs') plt.ylabel('Loss') plt.xticks(range(0, 210, 10)) plt.plot(hist.epoch, hist.history['loss']) plt.plot(hist.epoch, hist.history['val_loss']) plt.legend(['Loss', 'Validation Loss']) plt.show() plt.figure(figsize=(15, 5)) plt.title('Epoch-Mean Absolute Error Graph', fontsize=14, fontweight='bold') plt.xlabel('Epochs') plt.ylabel('Loss') plt.xticks(range(0, 210, 10)) plt.plot(hist.epoch, hist.history['mae']) plt.plot(hist.epoch, hist.history['val_mae']) plt.legend(['Mean Absolute Error', 'Validation Mean Absolute Error']) plt.show() eval_result = model.evaluate(scaled_test_dataset_x, test_dataset_y) for i in range(len(eval_result)): print(f'{model.metrics_names[i]}: {eval_result[i]}') #---------------------------------------------------------------------------------------------------------------------------- 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 kümesinin sütunları ile kestirilecek nicelik arasında doğrusal bir ilişki yoksa bu yöntemin başarısı düşmektedir. 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. Ancak diğer bir seçenek de veri kümesinde hedef değişken ile düşük korelasyona sahip sütunların atılması yalnızca yüksek korelasyona sahip sütunların doğrusal regresyona sokulmasıdır. Böyle bir yaklaşım tüm sütunların doğrusal regresyona sokulmasındna daha iyi sonuçlar vermektedir. Çoklu doğrusal regresyonda X değişkenlerinin sayısı arttıkça (yani veri kümesindeki sütunların sayısı arttıkça) regresyonun temsil yeteneği azalmaktadır. Bu nedenle bu yöntem çok sayıda sütuna sahip veri kümelerinde dikkatlice uygulanmalıdır. Bu tür durumlarda yukarıda da belirttiğimiz gibi hedef değişken ile düşük korelasyona sahip sütunların atılmasıyla bir özellik seçimi yapılabilir. Aşağıdaki örnekte make_blobs fonksiyonu ile küresel noktalar üretilip bu noktalar üzerinde doğrusal regresyon uygulanmıştır. Tabii R^2 değeri çok düşük çıkmıştır. X ve Y verileri arasında doğrusal bir ilişkiye benzeyen bir ilişki yoksa doğrusal regresyon uygulamaya çalışmamalısınız ya da düşük korelasyonlu sütunları atarak yalnızca yüksek korelasyona sahip sütunları doğrusal regresyona sokmalısınız. Doğrusal regresyonun uygulanamadığı bu tür durumlarda yapay sinir ağları ya da "destek vektör makineleri (support vector machines)" gibi diğer yöntemleri tercih edebilirsiniz. #---------------------------------------------------------------------------------------------------------------------------- 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('Linear 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 veri kümemizde kategorik (nominal) sütunlar varsa ne yapmalıyız? Bu sütunları doğrudan LabelEncoder ile sayısal hale getirip doğrusal regresyona sokmak iyi bir fikir değildir. Örneğin bir sütunda "Kırmızı", "Yeşil", "Mavi" gibi etketler bulunuyor olsun. Burada Kırmızı = 0, Mavi = 1, Yeşil = 2 ilişkisinin doğrusal regresyon için hiçbir anlamı yoktur. Bu tür durumlarda "one hot encoding" dönüştürmesinin uygulandığını anımsayınız. O halde biz de kategorik sütunları önce "one hot encoding" dönüştürmesine sokup sonra doğrusal regresyon uygulayabiliriz. Tabii aslında doğrusal regresyon tipik olarak nümerik sütunların söz konusu olduğu veri kümeleri için uygun yöntem olmaktadır. Kategorik sütunlara sahip veri kümelerinde bu kategorik sütunlar doğrusal regresyon uygulamadan önce tamamen de atılabilir. Ancak bu da regresyonun sonucunu etkileyebilecektir. #---------------------------------------------------------------------------------------------------------------------------- #---------------------------------------------------------------------------------------------------------------------------- Pekiyi veri kümemiz 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. Biz bu veri kümesinin doğrusal regresyona uygunluğunu nasıl belirleyebiliriz? İş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. Bu durumda ya doğrusal regresyon uygulamaktan vazgeçeriz ya da yalnızca Y değerleriyle yüksek korelasyona sahip sütunları doğrusal regresyona sokarak duruma bakabiliriz. Ö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 uygulamacı ne yapabilir? İşte bu tür durumlarda yukarıda da belirttiğimiz gibi 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ğinin çizilmesi de görsel olarak tespitin yapılmasını kolaylaştıracaktır. Çoklu doğrusal regresyonda eğer sütun sayısı fazla ise veri kümesindeki satır sayısı da fazla olmaldıdır. Başka bir deyişle sütun sayısının satır sayısına oranı yüksek olmamalıdır. Eğer özellik seçimi yapıldıktan sonra hala bu oran yüksek kalıyorsa doğrusal regresyon uygulamaktan vazgeçilebilir. Aşağıdaki örnekte "Boston Housing Prices" veri kümesinde X sütunlarıyla Y arasındaki korelasyonlar incelenmiş ve 0.45'ten büyük olan sütunlar alınarak onlarla doğrusal regresyon uygulanmıştır. Bu sonuç tüm sütunların alınmasıyla elde edilen sonuçtan biraz daha iyi olmaktadır. #---------------------------------------------------------------------------------------------------------------------------- CORR_THREASHOLD = 0.45 import numpy as np 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() concat_dataset = np.concatenate((training_dataset_x, training_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) 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) 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 korelasyonu 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 korelasyon olanları seçtikten sonra ayrıca seçilenlerin arasındaki korelasyonlara da bakmalıyız. Eğer seçilenler arasında yüksek korelsyona sahip olanlar varsa onların da bir tanesini muhafaza ederek diğerlerini atabiliriz. Pekiyi aralarında yüksek korelasyon olan sütunları nasıl tespit edebiliriz? İlk akla gelen yöntem korelasyon matrisini elde edip matrisin hücrelerine bakmak, yüksek değerli hücrelerin bulunduğu sütunlardan birini atıp diğerini seçmek olabilir. Bu durumda seçilecek sütun hedef değişkenle korelasyonu daha yüksek olan sütun olabilir. Tabii C1 sütunu ile C2 sütunu arasında, C2 sütunu ile de C3 sütunu arasında yüksek bir korelasyon olabilir. Bu durumda eğer biz C1 sütununu atacaksak C2 ile C3 arasında da yüksek korelasyon olduğu için C2 ya da C3'ten birini de atmak isteyebiliriz. 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 (VIF)" 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 bir 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ında default olarak bulunmamaktadır. Kütüphaneyi pip programı ile şöyle kurabilirsiniz. pip install statsmodels variance_inflation_factor fonksiyonun iki parametresi vardır. Birinci parametre X verilerinin bulunduğu matrisi, ikinci 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 biz iki yöntemi karma edebiliriz. Yani biz hem Y ile yüksek korelasyonu olan X sütunlarını alabiliriz hem de kendi aralarında yüksek korelasyonu olan X sütunlarını atabiliriz. Bu tür durumlarda hedef değişkenle yüksek korelasyonu olan sütunların seçilmesi genel olarak doğrusal regresyon için daha etkilidir. (Yani önce hedef değişkenle yüksek korelasyonlu sütunları belirleyip sonra onların arasında yüksek korelasyon olan sütunları elimine etmek uygun olabilir.) Aşağıdaki örnekte VIF değerlerine bakılarak aralarında yüksek korelasyon bulunan sütunlardan birisi atılmıştır. Ancak bu örnekte önemli bir sorun vardır. Yukarıda da belirttiğimiz gibi eğer biz yalnızca sütunların kendi aralarındaki korelasyonlarına bakarsak hiç hedef değişkenle olan koreleasyonlara bakmazsak VIF yöntemiyle seçilen sütunların hedef değişkenle korelasyonu düşük olabilir. Bu da kötü bir sonucun elnde edilmesine yol açacaktır. Aşağıda yalnızca yüksek korelasyonu olan sütunların atılmasıyla doğrusal regresyon uygulanmıştır. Bu da kötü bir sonucun elde edilmesine yol açmış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((training_dataset_x, training_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) selected_training_dataset_x = training_dataset_x[:, selected_cols] selected_test_dataset_x = test_dataset_x[:, selected_cols] from sklearn.linear_model import LinearRegression from sklearn.metrics import mean_absolute_error lr = LinearRegression() lr.fit(selected_training_dataset_x, training_dataset_y) predict_result = lr.predict(selected_test_dataset_x) 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}') #---------------------------------------------------------------------------------------------------------------------------- Şimdi de "Boston Housing Prices" veri kümesinde hem hedef değişkenle yüksek korelasyonu olan sütunları alalım hem de bunların arasında yüksek korelasyonu olan sütunları elimine edelim. Ancak bu iki yöntemi uygularken elimizde hiç sütun da kalmayabilir. Çünkü hedef değişkenle yüksek korelasyonu olan sütunlar elde edildikten sonra bunlar arasındaki korelasyonlar da yüksek olabilir. Bu durumda eşik değerlerini yükseltebilirsiniz. Aşağıdaki örnekte önce hedef değişkenle yüksek korelasyona sahip sütunlar alınıp sonra da kendi aralarında yüksek korelasyona sahip olan sütunlar VIF değerlerinden hareketle atılmıştır. Biz burada VIF için eşik değerini 10 olarak aldık. Bunun sonucunda örneğimizde yalnızca iki sütun seçildi. Ancak regresyonumuzun başarısı iyi olmadı. Çünkü iki sütunun seçilmesi önemli bir bilgi kaybı oluşturdu. Tabii bu örnekte eşik değerlerini değiştirerek daha iyi sonuçlar oluşturabilirsiniz. #---------------------------------------------------------------------------------------------------------------------------- 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') import numpy as np 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) concat_dataset = np.concatenate((training_dataset_x, training_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() corr_selected_cols, = np.where(corr[:-1, -1] > CORR_THREASHOLD) print(corr_selected_cols) from statsmodels.stats.outliers_influence import variance_inflation_factor vifs = np.array([variance_inflation_factor(training_dataset_x[:, corr_selected_cols], i) for i in range(len(corr_selected_cols))]) for i, vif in enumerate(vifs): print(f'{corr_selected_cols[i]} ---> {vif}') final_selected_indexes, = np.where(vifs < 10) print(corr_selected_cols[final_selected_indexes]) final_selected_training_dataset_x = training_dataset_x[:, corr_selected_cols[final_selected_indexes]] final_selected_test_dataset_x = test_dataset_x[:,corr_selected_cols[final_selected_indexes]] from sklearn.linear_model import LinearRegression from sklearn.metrics import mean_absolute_error lr = LinearRegression() lr.fit(final_selected_training_dataset_x, training_dataset_y) predict_result = lr.predict(final_selected_test_dataset_x) mae = mean_absolute_error(predict_result, test_dataset_y) print(f'Mean Absolute Error: {mae}') r2 = lr.score(final_selected_test_dataset_x, test_dataset_y) print(f'R^2 = {r2}') #---------------------------------------------------------------------------------------------------------------------------- Matematiksel optimizasyon problemlerinin çözümü temelde iki yöntemle yapılmaktadır: 1) Kapalı (closed) yöntemle (buna sembolik ya da analitik yöntem de denilmektedir). 2) Nümerik yöntemle. Çözümün eşitlikler ya da formüllerle sembolik bir biçimde oluşturulmasına "kapalı çözüm" denilmektedir. Kapalı çözüm oluşturulduğunda belli değerler için için sonuçlar bu değerlerin formülde yerine konulmasıyla elde edilir. Yani kapalı çözüm genel bir biçimde yapılır ve bu çözümden genel bir eşitlik ya da formül elde edilir. Örneğin f(x) = 3x^2 - 5x + 7 fonksiyonun türevi kapalı yöntemle f'(x) = 6x - 5 biçiminde bir fonksiyon olarak elde edilmektedir. Biz de asıl fonksiyonun belli bir noktasındaki türevini bu noktayı türev fonksiyonunda yerine koyarak elde ederiz. Örneğin doğrusal regresyonun çözümünde kullanılan en küçük kareler yöntemi bir kapalı çözüm vermektedir. Anımsanacağı gibi biz bu kapalı çözümden (X.T @ X)^-1 @ X.T @ y biçiminde bir formül elde etmiştik. Sonra da somut bir problemi çözmek için değerleri bu formülde yerine koymuştuk. Ancak maalesef her türlü problemin bu biçimde formülsel bir kapalı çözümü oluşturulamamaktadır. Bazı problemlerin kapalı çözümleri bir biçimde yapılabilse bile bunlar artık uygulanabilirlikten uzaklaşabilmektedir. İşte bu tür problemlerde diğer bir çözüm yöntemi "nümerik analiz" yöntemi ya da kısaca "nümerik" yöntemdir. Nümerik yöntemlerde problemi çözecek bir formül bulmak yerine çözüm iteratif bir biçimde gittikçe iyileştirerek elde edilmektedir. Tabii nümerik yöntemlerin etkin kullanılabilmesi ancak programalama yoluyla mümkündür. Şüphesiz kapalı çözümler daha kesin bir sonuca varılmasını sağlamaktadır. Bu çözüm çoğu durumda çok daha hızlı olma eğilimindedir. Ancak seyrek de olsa bazı durumlarda kapalı yöntemler nümerik analiz yöntemlerine göre daha yavaş da kalabilmektedir. Nümerik çözümler iteratif olduğu için hedeflenen sonuca yavaş yavaş yaklaşmayı sağlarlar. Dolayısıyla gerçek sonuç ile elde edilen sonuç arasında farklar oluşabilmektedir. Yukarıda da belirttiğimiz gibi pek çok problemin kapalı çözümünün mümkün olmaması nümerik çözümlerin kullanılmasını zorunlu hale getirmektedir. #---------------------------------------------------------------------------------------------------------------------------- #---------------------------------------------------------------------------------------------------------------------------- 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 daha bulunmaktadır: Lasso Regresyonu, Ridge Regresyonu ve Eleastic Net 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 biçimde yapmaya çalıştığımız "özellik seçimlerini" kendisi yapmaktadır. Lasso regresyonunda en küçük kareler yöntemine eklenen bu ceza puanına "L1 Düzenlemesi (L1 regulation)" denilmektedir. Lasso regresyonundaki en küçüklenecek amaç fonksiyonu (ya da "loss" fonksiyonu) aşağıdaki biçimde ifade edilebilir: J(β) = Σ(yᵢ - ŷᵢ)² + λ * Σ|βⱼ| Burada aslında ifadenin baş tarafı en küçük kareler formülüdür. Bu formüle L1 düzenlemesi (L1 Regulation) denilen aşağıdaki terim eklenmiştir: λ * Σ|βⱼ| Burada Σ|βⱼ| ifadesi katsayıların mutlak değerlerinin toplamını belirtmektedir. λ ise bir hyper parametredir. Eğer λ = 0 olursa Lasso regresyonunun en küçük kareler regrsyonundan bir farkı kalmaz. λ parametresinin değeri artırıldıkça daha fazla sütun elimine edilecektir. Uygulamacı çeşitli λ değerleri içinmodeli deneyip deneme yanılma yöntemiyle uygun λ değerini belirlemeye çalışabilir. Lasso regresyonunda "özellik ölçeklemesi" gerekmektedir. Genel olarak "standart ölçekleme (standard scaling)" tercih edilmektedir. Ancak "Min-Max ölçeklemesi" de benzer sonuçları verebilmektedir. #---------------------------------------------------------------------------------------------------------------------------- #---------------------------------------------------------------------------------------------------------------------------- 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ında 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 değerleiyle çağrılır. Yine tahminleme için predict metodu kullanılmaktadır. Oluşturulan hyperplane'in katsayı değerleri coef_ ve intercept_ örnek özniteliklerinden 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ı sıfır olacaktır. Bu katsayılırın sıfır olması aslında o sütunun tamamen atıldığı anlamına gelmektedir. Lasso sınıfında alpha değerini 0 verdiğinizde bir uyarı mesajıyla karşılaşabilirisiniz. Çünkü bu durumda Lasso regresyonunun en küçük kareler yönteminden yaklaşımsal olarak bir farkı kalmamaktadır. Aşağıdaki örnekte "Boston Housing Prices" veri kümesi üzerinde Lasso regresyonu uygulanmıştır. Burada alpha parametresi 0.2 seçilmiştir. Bu seçim soncunda dört sütun atılmıştır. Bu örnekte veriler üzerinde standart ölçekleme de uygulanmıştır. Tabii kestirimde bulunulurken kestirilmek istenen X değerlerinin de yine aynı standart ölçeklemeye sokulması gerekmektedir. #---------------------------------------------------------------------------------------------------------------------------- ALPHA = 0.05 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 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 from sklearn.metrics import mean_absolute_error lasso = Lasso(ALPHA) 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) r2 = lasso.score(scaled_training_dataset_x, training_dataset_y) print(f'R^2: {r2}') mae = mean_absolute_error(test_predict_result, test_dataset_y) print(f'Mean Absolute Error: {mae}') 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(f'Predict Result: {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. Bunun "grid search" gibi sistematik yöntemler de kullanılmaktadır. Biz ileride "grid search" yönteminin üzerinde duracağız. Aşağıdaki örnekte "Boston Housing Prices" 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ğeri doğrusal regresyonun iyiiğine ilişkin genel bir ölçüt olmasına karşın spesifik bir veri kümesi için mutlak anlamda bir belirleme için kullanılamamaktadır. Yukarıda da belirttiğimiz gibi alpha = 0 değeri için Lasso sınıfı bir uyarı vermektedir. Bu uyarı mesajını dikkate almayınız. Programın çalıştırılmasıyla aşağıdaki gibi bir çıktı elde edilmiştir: Alpha: 0, MAE: 3.427549362182617, R^2: 0.7427978515625 Alpha: 0.05, MAE: 3.337947368621826, R^2: 0.7414087057113647 Alpha: 0.1, MAE: 3.295764684677124, R^2: 0.7375916838645935 Alpha: 0.15000000000000002, MAE: 3.2998266220092773, R^2: 0.7311793565750122 Alpha: 0.2, MAE: 3.33180832862854, R^2: 0.7246889472007751 Alpha: 0.25, MAE: 3.3882341384887695, R^2: 0.7197294235229492 Alpha: 0.3, MAE: 3.444835662841797, R^2: 0.7149202823638916 Alpha: 0.35, MAE: 3.4982802867889404, R^2: 0.7100659608840942 Alpha: 0.39999999999999997, MAE: 3.547013521194458, R^2: 0.7054669857025146 Alpha: 0.44999999999999996, MAE: 3.564919948577881, R^2: 0.7023859620094299 Alpha: 0.49999999999999994, MAE: 3.58282732963562, R^2: 0.6989423036575317 Alpha: 0.5499999999999999, MAE: 3.600729465484619, R^2: 0.6951364278793335 Alpha: 0.6, MAE: 3.6019318103790283, R^2: 0.6927566528320312 Alpha: 0.65, MAE: 3.598907470703125, R^2: 0.6906068325042725 Alpha: 0.7000000000000001, MAE: 3.5949909687042236, R^2: 0.6883739233016968 Alpha: 0.7500000000000001, MAE: 3.5910913944244385, R^2: 0.6859737634658813 Alpha: 0.8000000000000002, MAE: 3.587191104888916, R^2: 0.6834081411361694 Alpha: 0.8500000000000002, MAE: 3.583289861679077, R^2: 0.6806771755218506 Alpha: 0.9000000000000002, MAE: 3.579387903213501, R^2: 0.6777805685997009 Alpha: 0.9500000000000003, MAE: 3.5754871368408203, R^2: 0.67471843957901 Alpha: 1.0000000000000002, MAE: 3.572746992111206, R^2: 0.6714907884597778 Alpha: 1.0500000000000003, MAE: 3.570899248123169, R^2: 0.6680976152420044 Alpha: 1.1000000000000003, MAE: 3.57242488861084, R^2: 0.6645389199256897 Alpha: 1.1500000000000004, MAE: 3.576953649520874, R^2: 0.660814642906189 Alpha: 1.2000000000000004, MAE: 3.5821940898895264, R^2: 0.6569249629974365 Alpha: 1.2500000000000004, MAE: 3.58744215965271, R^2: 0.6528698205947876 Alpha: 1.3000000000000005, MAE: 3.5923964977264404, R^2: 0.6496732234954834 Alpha: 1.3500000000000005, MAE: 3.5976717472076416, R^2: 0.646392822265625 Alpha: 1.4000000000000006, MAE: 3.602947950363159, R^2: 0.6429885625839233 Alpha: 1.4500000000000006, MAE: 3.6082236766815186, R^2: 0.639460563659668 Alpha: 1.5000000000000007, MAE: 3.614792823791504, R^2: 0.6363101601600647 Alpha: 1.5500000000000007, MAE: 3.6226513385772705, R^2: 0.633413553237915 Alpha: 1.6000000000000008, MAE: 3.6321372985839844, R^2: 0.6304188966751099 Alpha: 1.6500000000000008, MAE: 3.642801284790039, R^2: 0.6273297071456909 Alpha: 1.7000000000000008, MAE: 3.653465747833252, R^2: 0.6241455078125 Alpha: 1.7500000000000009, MAE: 3.665154457092285, R^2: 0.6208661794662476 Alpha: 1.800000000000001, MAE: 3.677025318145752, R^2: 0.6174920797348022 Alpha: 1.850000000000001, MAE: 3.689149856567383, R^2: 0.6140228509902954 Alpha: 1.900000000000001, MAE: 3.703413963317871, R^2: 0.6104587316513062 Alpha: 1.950000000000001, MAE: 3.7179884910583496, R^2: 0.6067994832992554 Bu örnek programda "mean absolute error" değerinin en küçük olduğu alpha değerinin 0.1 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.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=54321) 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 from sklearn.metrics import mean_absolute_error alpha = 0 while alpha < 2: lasso = Lasso(alpha) lasso.fit(scaled_training_dataset_x, training_dataset_y) test_predict_result = lasso.predict(scaled_test_dataset_x) r2 = lasso.score(scaled_training_dataset_x, training_dataset_y) mae = mean_absolute_error(test_predict_result, test_dataset_y) print(f'Alpha: {alpha}, MAE: {mae}, R^2: {r2}') alpha += 0.05 #---------------------------------------------------------------------------------------------------------------------------- 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 (ayni sütunların Beta katsayılarını sıfırlamaz), 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)" de denilmektedir. Ridge regresyonundaki ceza teriminde de bir lambda parametresi vardır. Yine bu lamda parametresi yükseltilirse daha fazla sütunun etkisi azaltılmaktadır. Yani lamda değeri yükseltilirse daha fazla sütunun katsayı değeri 0'a yaklaştırılmakta, düşürülürse daha az sütunun katsayı değeri 0'a yaklaştırılmaktadır. Ridge regresyonunun amaç fonksiyonu (ya da "loss" fonksiyonu) şöyledir: J(β) = ∑(yᵢ - ŷᵢ)² + λ * ∑(βⱼ²) Burada L2 regülasyonu denilezn ceza terimi aşağıdaki gibidir: λ * ∑(βⱼ²) Burada mutlak değer yerine kare alma işlemi optimizasyon sırasında sütunun katsayısını sıfırlamak yerine onun etkisini düşürmektedir. 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 regresyonunda L2 regülasyon teriminde kare alma ifadesi olduğu çin lamda değeri Lasso regresyonuna göre daha büyük tutulabilir. #---------------------------------------------------------------------------------------------------------------------------- #---------------------------------------------------------------------------------------------------------------------------- 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 Prices" veri kümesi üzerinde üzerinde Ridge regresyonu uygulanmıştır. Bu örnekte alpha parametresi değişik değerlerle deneyiniz. alpha parametresi yükseltildikçe daha fazla sütun değeri sıfıra yaklaştırılacaktır. #---------------------------------------------------------------------------------------------------------------------------- ALPHA = 5 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=54321) 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 from sklearn.metrics import mean_absolute_error ridge = Ridge(ALPHA) 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) r2 = ridge.score(scaled_training_dataset_x, training_dataset_y) print(f'R^2: {r2}') mae = mean_absolute_error(test_predict_result, test_dataset_y) print(f'Mean Absolute Error: {mae}') 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(f'Predict Result: {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 regülayonu hem de L2 regülasyon terimi bulunmaktadır. Dolayısıyla modelin lambda1 ve lambda2 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 ayarlama yapma gereksinimini doğurmaktadır. Elastic Net regresyon modelinin amaç fonksiyonu (ya da "loss" fonksiyonu) şöyle ifade edilebilir: Loss(β) = ∑(yᵢ - ŷᵢ)² + λ1 * Σ|βⱼ| + λ2 * ∑(βⱼ²) #---------------------------------------------------------------------------------------------------------------------------- #---------------------------------------------------------------------------------------------------------------------------- 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 regülasyonu üzerinde hem de L2 regülasyonu üzerinde etkili olmaktadır. l1_ratio parametresi ise L1 regülasyonunun L2 düzenlemesine göre etkisini belirlemektedir. l1_ratio default olarak 0.5 değerindedir. Yani bu durumda L1 regülasyonu da L2 regülasyonu da 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 Prices" veri kümesinde Elastic Net regresyon modeli uygulanmıştır. Parametreler doğru ayarlanırsa Elstic Net diğerlerinden daha iyi sonuç verebilmektedir. Ancak parametrelerin ayarlanması daha zahmetlidir. Bunun için pek çok deneme yanılmanın yapılması gerekebilmektedir. #---------------------------------------------------------------------------------------------------------------------------- ALPHA = 0.1 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=54321) 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}') elastic = ElasticNet(alpha=0.1, l1_ratio=ratio) elastic.fit(scaled_training_dataset_x, training_dataset_y) test_predict_result = elastic.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 = elastic.score(scaled_training_dataset_x, training_dataset_y) print(f'R^2: {r2}') print('-' * 20) #---------------------------------------------------------------------------------------------------------------------------- #---------------------------------------------------------------------------------------------------------------------------- 105. Ders - 01/03/2025 - Cumartesi #---------------------------------------------------------------------------------------------------------------------------- #---------------------------------------------------------------------------------------------------------------------------- Biz şimdiye kadar doğrusal regresyon gördük. Doğrusal regresyon noktaları ortalayan bir doğru denkleminin 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 ki 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şlemler genel itibari ile benzerdir. n'inci dereceden tek değ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ı (yani 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 3'üncü dereceden bir polinomun genel biçimi şöyledir: P(x1, x2) = a00 + a10 x1 + a01 x2 + a11 x1 x2 + a20 x1^2 + a02 x2^2 + a21 x1^2 x2 + a12 x1 x^2 n'inci dereceden iki değişkenli bir polinomun genel biçimini daha sembolik olarak şöyle de ifade edebiliriz: P(x1, x2) = ∑ aij x1^i x2^j Burada i + j <= n olmalıdır. m tane değişkene sahip n'inci dereceden polinomun genel biçimini de sembolik olarak şöyle ifade edebiliriz: P(x1, x2, ..., xm) = ∑ ak1,k2,k3,...,km x1^k1 x2^k2 ... xm^km Burada üslerin toplamı olan k1 + k2 + k3 + ... + km değeri <= n'dir. 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 parametrelerinin tahmini zorlaşmaktadır. Bir polinomun derecesi onun en yüksek üssüyle belirtilmektedir. Pekiyi biz noktalarımızı için kaçıncı dereceden bir polinomla temsil etmeliyiz? Şü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ı da 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. Bu nedenle polinomun derecesi 2 ile başlatılıp 3 ve 4 değerleri de dikkate alınanbilir. Ancak daha yüksek derecelere çıkmak pek çok durumda kestirimin başarısını yükseltmeyecek bilakis düşürebilecektir. Tabii uygulamacının önce noktaları inceleyip doğrusal regresyonun uygun olup olmadığına bakması önerilir. Eğer bağımsız değişkenlerle bağımlı değişken arasında doğrusal ilişki varsa doğrusal regresyon uygulanmalıdır. (Tabii doğrusal regresyonu derecesi 1 olan polinomsal regresyon gibi de düşünebilirsiniz.) #---------------------------------------------------------------------------------------------------------------------------- #---------------------------------------------------------------------------------------------------------------------------- Pekiyi polinomsal regresyonun katsayı değerleri nasıl 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 polinomu şöyle ifade edebiliriz: P(x1, x2) = a0 + a1 x1 + a2 x2 + a3 x1 x2 + a4 x1^2 + a5 x2^2 + a6 x1^2 x2 + a6 x1 x^2+ a8 x1^3 + a9 X2^3 Burada görükldüğü gibi 7 tane katsayı değerinin tahmimn edilmesi gerekmektedir. Pekiyi bu değerler matematiksel olaraK nasıl tahmin edilecektir? İşte aslında 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 dönüştürme (polynomial transformation)" denilmektedir. Polinomsal dönüştürmede temel mantık değişkenlerin katsayı yapılması katsayıların değişken yapılmasıdır. örneğin yukarıda 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 dönüştürmede nasıl yapıldığını basit örnekle açıklayalım. Tek değişken için elimizde şöyle veri kümesi olsun: X Y ----- ---- 2 4 4 32 7 72 9 102 ... ... Burada biz grafiği çizdiğimizde bu noktaların bir doğru ile temsil edilemeyeceğini görmüş olalım. Bunun ikinci derece bir polinom oluşturmaya çalışalım. Bu ikinci derece polinomun genel biçimi şöyle olacaktor: a0 + a1 x + a2 x^2 Şimdi biz bu polinomda katsayılarla değişkenleri yer değiştirelim: a0 + x a1 + x^2 a2 Artık burada çoklu doğrulsa regresyon söz konusudur. Bu durumda yukarıdai X katsayıları çoklu doğrusal regresyonda şu biçime dönüşecektir: A0 A1 A2 1 2 4 1 4 16 1 7 49 1 9 81 ..... Görüldüğü gibi tek değişkenli 2'inci dereden polinom 2 değişkenli çok doğrusal regresyon haline dönüştürülmüştür. Şimdi bu doğrusal regresyon çözülürse biz aslında buradan a0, a1, a2 değerlerini elde etmiş olacağız. Burada ilk sütunun 1'lerden oluşması size biraz tuhaf gelebilir. Biz modeli bu katsayılarla doğrusal regresyona dönüştürdüğümüzde aslında polinomsal regresyondaki a0 değerini tahmin etmiş oluruz. Tabii doğrusal regresyonun ayrı bir intercept değeri olacaktır. Ancak bu intercept bizim bütün bu A0, A1, A2 değerlerini tahmin etmemiz için gerekmektedir. Pekiyi polinom 2 değişkenli olsaydı 2'inci derece polinomsal transformasyondönüştürme nasıl yapılacaktı? 2 değişkenli 2'inci derece polinomun genel biçimi şöyledir: a0 + a1 x1 + a2 x2 + a3 x1 x2 + a4 x1^2 + a5 x2^2 Buradaki X1 ve X2 gözlemlerinin şöyle olduğunu varsayalım: X1 X2 Y --- --- --- 1 2 8 3 3 12 4 1 11 6 2 23 ..... Yukarıdaki 2 dğişkenli polinomu katsayılarla değişkenleri ters çevirirsek şöyle yazabiliriz: a0 + x1 a1 + x2 a2 + x1 x2 a3 + x1^2 a4 + x2^2 a5 Bu durumda polinomsal dönüştürme şöyle yapılacaktır: A0 A1 A2 A3 A4 A5 --------------------------- 1 1 2 2 1 4 1 3 3 9 9 9 1 4 1 4 16 1 1 6 2 12 36 4 İşte görüldüğü gibi aslında polinomsal regresyon çoklu doğursal regresyona dönünüştürülerek çözülebilmektedir. Genel olarak m değişkene sahip n'inci dereceden polinomsal regresyon doğrusal regresyona dönüştürüldüğünde C(m + n, n) kadar tahmin edilecek katsayı oluşaaktadır. Örneğin 2 değişkenli 2'inci dereceden bir polinom doğrusal regresyona dönüştürüldüğünde C(2 + 2, 2) = 6'dır. #---------------------------------------------------------------------------------------------------------------------------- #---------------------------------------------------------------------------------------------------------------------------- Polinomsal regresyon sckit-learn kütüphanesinde iki aşamada çözülür. Birinci aşamada X katsayı matrisi "polinomsal dönüştürmeye" sokularak doğrusal regresyon için kullanılacak değerler elde edilir. İkinci aşamada ise doğrusal regresyon çözümü yapılır. Polinomsal regresyon için polinomsal dönüştürme 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) Metodun ilk parametresi noktaların kaçıncı dereceden bir polinom için dönüştürüleceğini belirtmektedir. Metodun include_bias parametresi dönüştürme sonucunda elde edilecek matrisin ilk sütununun 1 olup olmayacağını belirtmektedir. Bu parametrenin default değeri True ise (default değerin True olduğuna dikkat ediniz) bu durumda elde edilen matrisin ilk sütunu 1 olur. Daha sonra işlemler diğer scikit-learn sınıflarında 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 tane satıra sahip olacaktır. Ancak sütunları polinomun terim sayısı kadar olacaktır. Yukarıda bu sayının C(n + m, n) tane olduğunu belirtmiştik. Örneğin 6 tane satırdan oluşan 2 değişkenli bir veri kümesini 2'inci derece bir polinom için dönüştürmek isteyelim. Bu durumda PolinomialFetures işleminden sonra biz 6x6'lık bir matris elde ederiz. Elde ettiğimiz matrisin ilk sütununun default durumda 1 olacağını anımsayınız. Örneğin veri kümemizdeki X değerlerinin "points.csv" dosyası içerisinde aşağıdaki gibi olduğunu varsayalım: X1,X2 1,2 3,3 4,1 6,2 9,4 11,3 Bu veri kümesini aşağıdaki gibi PolynomialFeatures sınıfına sokalım: dataset_x = pd.read_csv('points.csv') pf = PolynomialFeatures(2) transformed_dataset_x = pf.fit_transform(dataset_x) print(transformed_dataset_x ) Elde edilen matris şu biçimdedir: [[ 1. 1. 2. 1. 2. 4.] [ 1. 3. 3. 9. 9. 9.] [ 1. 4. 1. 16. 4. 1.] [ 1. 6. 2. 36. 12. 4.] [ 1. 9. 4. 81. 36. 16.] [ 1. 11. 3. 121. 33. 9.]] #---------------------------------------------------------------------------------------------------------------------------- import pandas as pd from sklearn.preprocessing import PolynomialFeatures dataset_x = pd.read_csv('points.csv') pf = PolynomialFeatures(2) transformed_dataset_x = pf.fit_transform(dataset_x) print(transformed_dataset_x ) #---------------------------------------------------------------------------------------------------------------------------- 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. Ancak eğer LinearRegression işlemine verilen matrisin ilk sütunu 1 ise bu durumda coef_[0] değeri yerine intercept_ değeri kullanılmalıdır. İzleten paragraflarda bu konu üzerinde duracağız. Doğrusal regresyon sonucunda elde edilen R^2 değeri yine bize polinomsal regresyonun uygunluğu konusunda bilgi verebilecektir. Şimdi tek değişkenli bir veri kümesi için polinomsal regresyona örnek verelim. Örneğimizdeki "points.csv" dosyasının içeriği şöyle olsun: X,Y 4,-200 -3,-51 -2,-19 -1,-6 0,-5 1,-5 2,1 3,22 4,56 5,78 6,176 7,271 8,401 9,572 10,713 11,1042 12,1333 13,1623 14,2015 15,215 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.58 civarındadır. #---------------------------------------------------------------------------------------------------------------------------- import numpy as np import pandas as pd df = pd.read_csv('points.csv') dataset_x = df['X'].to_numpy() dataset_y = df['Y'].to_numpy() from sklearn.linear_model import LinearRegression lr = LinearRegression() lr.fit(dataset_x.reshape(-1, 1), dataset_y) x = np.linspace(0, 18, 1000) 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.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) rsquare = lr.score(dataset_x.reshape(-1, 1), dataset_y) print(f'R^2: {rsquare}') #---------------------------------------------------------------------------------------------------------------------------- Şimdi de yukarıdaki verileri 3'üncü derece bir polinomla temsil etmeye çalışalım . Tek değişkenli 3'üncü derece polinomun genel biçimi şöyledir: P(x) = a0 + a1 x + a2 x^2 + a3 x^3 Burada toplam 4 tane katsayı vardır. Bu durumda biz polinomsal dönüştürme yaptığımızda 4 sütunlu bir veri kümesi elde ederiz. O halde aslında uygulayacağımız doğrusal regresyon sanki 4 sütunlu doğrusal regresyon gibidir. Örneğin: df = pd.read_csv('points.csv') dataset_x = df['X'].to_numpy() dataset_y = df['Y'].to_numpy() 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 olacaktır: P(x) = lr.intercept_ + lr.coef_[1] * x + lr.coef_[2] * x ** 2 + lr.coef_[3] * x ** 3 Tabii kestirim 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: X,Y 4,-200 -3,-51 -2,-19 -1,-6 0,-5 1,-5 2,1 3,22 4,56 5,45 6,176 7,271 8,401 9,572 10,713 11,1042 12,1333 13,1623 14,2015 Örneğimizde kestirim işlemi iki biçimde de yapılmıştır. Birinci biçimde kestirilecek noktalar doğrudan elde edilen katsayı değerleriyle polinoma sokulmuştr: predict_data = np.array([12.9, 4.7, 6.9]) predict_result = lr.intercpt_ + lr.coef_[1] * predict_data + lr.coef_[2] * predict_data ** 2 + lr.coef_[3] * predict_data ** 3 print(predict_result) İkinci biçimde de kestirilecek değerler önce polinomsal dönüştürmeyle doğrusal regresyona uygun hale getirilmiş sonra LinearRegression sınıfının predict metodu kullanılmıştır: predict_data = np.array([[12.9], [4.7], [6.9]]) transformed_predict_data = pf.transform(predict_data) predict_result = lr.predict(transformed_predict_data) print(predict_result) #---------------------------------------------------------------------------------------------------------------------------- import numpy as np import pandas as pd df = pd.read_csv('points.csv') dataset_x = df['X'].to_numpy() dataset_y = df['Y'].to_numpy() 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) rsquare = lr.score(transformed_dataset_x, dataset_y) print(f'R^2: {rsquare}') x = np.linspace(-5, 20, 1000) transformed_x = pf.transform(x.reshape(-1, 1)) y = lr.predict(transformed_x) import matplotlib.pyplot as plt plt.title('Polynomial Regression') plt.scatter(dataset_x, dataset_y, color='blue') plt.plot(x, y, color='red') plt.show() predict_data = np.array([12.9, 4.7, 6.9]) predict_result = lr.intercept_ + lr.coef_[1] * predict_data + lr.coef_[2] * predict_data ** 2 + lr.coef_[3] * predict_data ** 3 print(predict_result) predict_data = np.array([[12.9], [4.7], [6.9]]) transformed_predict_data = pf.transform(predict_data) predict_result = lr.predict(transformed_predict_data) print(predict_result) #---------------------------------------------------------------------------------------------------------------------------- Burada LinearRegression sınıfının fit metoduna soktuğumuz matrisin ilk sütununun 1 olması üzerinde durmak isyiyoruz. Bu bağlamda birkaç durum söz konusudur: 1) fit metoduna verilen matrisin ilk sütunu 1 ise bu durumda elde edilen coef_ katsayılarının 0'ıncı indeksli elemanı her zaman 0 olur. Dolayısıyla sabit terim sınıfın intercept_ elemanından elde edilmelidir. 2) fit metoduna verilen matrisin ilk sütunu 1 ise fakat LinearRegression nesnesi yaratılırken fit_intercept parametresi False girilirse bu durumda sabit terim (yani intercept_ değeri) coef_[0]'da bulunacaktır. Tabii bu durumda nesnenin de intercept_ özniteliği 0 olur. 3) fit metoduna verilen matrisin ilk sütunu 1 değilse ve fit_intercept parametresi default değer olan True biçimindeyse bu durumda sabit terim (yani intercept) nesnenin intercept_ özniteliğinden alınır. Ancak coef_ dizisinin 0'ıncı indeksi artık sabit terimi (yani intercept'i) belirtmez. Sabit olmayan ilk terimin katsayısını belirtir. 4) fit metoduna verilen matrisin ilk sütunu 1 değilse ve fit_intercept parametresi False olarak girilirse bu durum anlamlı değildir. Yukarıdaki örneğimizde PolynomialFeatures sınıfı bize ilk sütunu 1 olan bir matris vermiştir. Default durumda LinearRegression sınıfının fit_intercept parametresi True olduğu için sabit terin nesnenin coef_[0] elemanındna değil intercept_ özniteliğinden alınmıştır. LinearRegression sınıfında predcit metodu zaten ilgili değerleri fit_intercept parametresini dikkate alarak doğru denkleminde yerine koyup sonucu vermektedir. Tabii bizim predcit yaparken verdiğimiz veri kümesinin fit yaparken verdiğimiz veri kümesi ile aynı biçimde oluşturulmuş olması gerekir. Örneğin biz LinearRegression sınıfının fit metodunu ilk sütunu 1 olan bir matris ile yapmışsak predict işleminde de yine ilk sütunu 1 olan bir matris kullanmalıyız. Zaten yukarıdaki örneğimizde biz predict işlemini yapmadan önce yine değerleri PolynomialFeatures sınıfının transform fonksiyonuna soktuğumuz için bir sorun oluşmamaktadır. #---------------------------------------------------------------------------------------------------------------------------- #---------------------------------------------------------------------------------------------------------------------------- Aşağıdaki örnekte çeşitli dereceler için polinamsal regresyon uygulanıp bunların grafikleri aynı eksende gösterilmiştir. Polinomun derecesi yükseltildikçe noktaları temsil eden daha iyi bir eğri elde edildiğini göreceksiniz. Ancak bu durum yanıltıcıdır. Derece yükseltildikçe söz konusu noktalara ilişkin daha iyi bir eğri elde ediliyor olsa da aslında "overfitting" oluşmaktadır. Yani elde edilen eğri bu noktalara özgü hale gelir ve kestirimin gücü azalır. R^2 değeri de bu bağlamda iyi gösterge oluşturmamaktadır. Polinomun derecesi gerektiğinden fazla yükseltildikçe test veri kümesi üzerinde uygulanan "mean squared error" gibi "mean absolute error" gibi metrikler düşmeye başlayacaktır. #---------------------------------------------------------------------------------------------------------------------------- import numpy as np import pandas as pd df = pd.read_csv('points.csv') dataset_x = df['X'].to_numpy() dataset_y = df['Y'].to_numpy() from sklearn.preprocessing import PolynomialFeatures from sklearn.linear_model import LinearRegression import matplotlib.pyplot as plt degrees = [2, 3, 4, 5] rsquares = [] plt.figure(figsize=(10, 8)) plt.title('Polynomial Regression') plt.scatter(dataset_x, dataset_y, color='blue') plt.ylim(-500, 3000) for i in degrees: pf = PolynomialFeatures(degree=i) transformed_dataset_x = pf.fit_transform(dataset_x.reshape(-1, 1)) lr = LinearRegression() lr.fit(transformed_dataset_x, dataset_y) x = np.linspace(-5, 20, 1000) transformed_x = pf.transform(x.reshape(-1, 1)) y = lr.predict(transformed_x) plt.plot(x, y) rsquares.append(lr.score(transformed_dataset_x, dataset_y)) plt.legend( ['points'] + [f'{degree} ({rs:.2f})' for degree, rs in zip(degrees, rsquares)]) plt.show() #---------------------------------------------------------------------------------------------------------------------------- #---------------------------------------------------------------------------------------------------------------------------- Veri kümesindeki sütunların sayısının 1 artırılması doğrusal polinomsal regresyondaki terim sayısının misliyle artmasına yol açmaktadır. Bir de derece yükseltildiğinde artış çok daha büyük olmaktadır. (Polinomsal regresondaki toplam terim sayısının n'nci dereceden m değişkenli polinom için C(n + m, n) olduğunu anımsayınız.) 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 Görüldüğü gibi polinomsal regresyon doğrusal regresyona dönüştürüldüğünde veri kümesinin sütun sayısı çok artabilmektedir. İşte bunun doğal sonucu da "overfitting" durumudur. #---------------------------------------------------------------------------------------------------------------------------- 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 Prices" veri kümesi üzerinde şu üç model denenmiştir: 1) LinearRegression 2) 2'inci Dereceden PolynomialFeatures --> LinearRegresson 3) 2'inci Dereceden PolynomialFeatures --> Lasso Elde edilen sonuçlar şöyle olmuştur: Mean Absolute Error: 3.5789294242858887 R^2 = 0.7665384411811829 Mean Absolute Error: 5.021183967590332 R^2 = 0.5885621309280396 Mean Absolute Error: 2.180431842803955 R^2 = 0.8993322253227234 Bu denemelerden en iyi sonuç ikinci derece polinomun Lasso regresyonu ile uygulanması olmuştur. Burada alpha değeri ile değişik performanslar elde edilmiştir. İyi bir alpha değeri 0.005 biçimindedir. Bu tür durumlarda çeşitli modellerin denenip en iyi sonucun kullanılması yoluna gidilebilir. #---------------------------------------------------------------------------------------------------------------------------- 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 from sklearn.metrics import mean_absolute_error from sklearn.preprocessing import StandardScaler from sklearn.linear_model import Lasso 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) 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) 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() 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) 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() #---------------------------------------------------------------------------------------------------------------------------- Polinomsal regresyon için önce PolynomialFeatures sınıfı sonra da regresyon sınıflarından birini kullanıyorduk. Aslında bu işlemleri daha önce görmüş olduğumuz boru hattı (pipeline) mekanizmasıyla birleştirirsek daha sade bir kod elde edebiliriz. Aşağıdaki örnekte "Boston Housing Prices" veri kğmesi üzerinde önce PolynomialFeatures işlemi sonra StandardScaler işlemi sonra da Lasso regresyon işlemi bir boru hattı içerisinde uygulnamış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 from sklearn.preprocessing import PolynomialFeatures from sklearn.preprocessing import StandardScaler from sklearn.linear_model import Lasso from sklearn.pipeline import Pipeline from sklearn.metrics import mean_absolute_error 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) pipeline = Pipeline(steps=[("pf", PolynomialFeatures(degree=2)), ('ss', StandardScaler()), ("lasso", Lasso(alpha=0.005, max_iter=100000))]) pipeline.fit(training_dataset_x, training_dataset_y) predict_result = pipeline.predict(test_dataset_x) mae = mean_absolute_error(predict_result, test_dataset_y) print(f'Mean Absolute Error: {mae}') r2 = pipeline.score(test_dataset_x, test_dataset_y) print(f'R^2 = {r2}') print() #---------------------------------------------------------------------------------------------------------------------------- Pekiyi sonuç olarak ne zaman polinomsal regresyon uygulamalıyız? Aslında yukarıda da belirttiğimiz gibi veri kümesi için çeşitli modellerin denemesi ve en iyi modelin uygulanması yani deneme yanılma yöntemi uygulamada esastır. Önce doğrusal regresyon üzerinde durulmalıdır. Eğer X sütunları ile bağımsız değişken arasında doğrusala benzer bir ilişki varsa doğrusal regresyon tercih edilmelidir. Eğer X değişkenin sütunları ile bağımsız değişken arasında doğrusal bir ilişki yoksa bu durumda polinomsal regresyon üzerinde durulabilir. Polinomsal regresyon için önce 2'inci derece sonra 3'üncü derece ve sonra da 4'üncü derece için denemeler yapılabilir. Derecenin 4'ün yukarısına çıkartılması "overfitting" yüzünden genellikle iyi sonuç vermeme eğilimindedir. Ayrıca derecenin 4'ün yukarısına çıkartılması gürültü noktalarının bulunduğu durumda eğrinin bu gürültü noktalarını da dikkate alacak biçim kıvrılmasına yol açabilir. Bunun sonucu oalrak da "overfitting" durumu yine oluşabilir. Ancak yukarıda da belirttiğimiz gibi nasıl bir regresyonun uygulanacağı kararının hiç deneme yanılma yoluna başvurmadan isabetli bir biçimde verilmesi de genellikle mümkün değildir. #---------------------------------------------------------------------------------------------------------------------------- #---------------------------------------------------------------------------------------------------------------------------- Makine öğrenmesinde sınıflandırma amacıyla uygulanan bir grup yönteme "doğrusal sınıflandırıcılar (linear classifiers)" denilmektedir. Doğrusal sınıflandırıcılar iki farklı sınıf içeren kümeyi (yani ikili sınıflandırmaya ilişkin kümeyi) bir doğru ile ayrıştırmayı sağlamaktadır. Tabii iki boyutlu uzaydaki doğruların üç boyutlu uzayda bir düzlem olduğunu ve her n boyutlu uzayın da bir düzleminin söz konusu olduğunu belirtmiştik. N boyutlu uzayın düzlemine genel olarak "hyperplane" denildiğini anımsayınız. O halde doğrusal sınıflandırıcılar genel olarak bir hyperplane ile veri kümesini birbirinden ayırmaya çalışmaktadır. Tabii bu tür sınıflandırıcıların iyi işlev görebilmesi için veri kümesinin de "doğrusal olarak ayrıştırılabilir (linearly separable) biçimde olması gerekir. Aksi takdirde ayrıştırmada kusurlar ortaya çıkacaktır bu da sınıflandırıcının performansını düşürecektir. Makine öğrenmesinde çok kullanılan iki doğrusal sınıflandırıcı vardır: 1) İstatstikte "lojistik regresyon (logistic regression)" ya da "logit regresyonu" denilen doğrusal sınıflandırıcı. 2) Destek Vektör Makineleri (Support Vector Machines)" denilen doğrusal sınıflandırıcı. Biz bu bölümde istatistiksel lojistik regresyon üzerinde duracağız. #---------------------------------------------------------------------------------------------------------------------------- #---------------------------------------------------------------------------------------------------------------------------- İstatistiksel lojistik regresyonda bağımısz değişken (yani Y değerleri) 0 ve 1 ile temsil edilmektedir. Burada 1 sınıflardan birini 0 ise diğer sınıfı temsil eder. Bu durumda örneğin iki boyutlu uzayda (yani 2 sütunlu bir veri kümesi) aşağıdakine benzer bir görünümde olacaktır: X1 X2 Sınıf ---- ---- ------ 10 7 1 7 4 0 2 6 1 1 9 0 ...... Lojistik regresyonda kesirim sırasında kestirilecek noktanın 1 olma olasılığı elde edilmektedir. Örneğin biz bir noktayı vererek kestirim yaptığımızda eğer kestirim sonucunda 0.7 gibi bir değer elde ediyorsak bu noktanın 1 olma olasılığı 0.7'dir, 0 olma olasılığı ise 0.3'tür. Biz bu noktayı sınıflandıracaksak 1'e daha yakın olduğu için 1 olarak sınıflandırırız. Verdiğimiz başka bir nokta için kestirilen olasılık örneğin 0.1 ise bu noktanın 1 olma olasılığı 0.1'dir. Bu durumda aslında 0 olma olasılığı da 0.9'dur. İşte kestirim yapılırken elde edilen olsalık 1'e daha yakınsa (yani > 0.5 ise) bunu 1 olarak, 0'a daha yakınsa (yani < 0.5 ise) bunu da 0 olarak kabul ederiz. İstatistikte (özellik şans oyunlarında) bir olayın gerçekleşme olasılığının gerçekleşmeme olasılığına oranına İngilizce "odds ratio" denilmektedir. Odds Ratio şöyle ifade edilebilir: Odds Ratio = p / (1 - p) Odds Ratio özellikle ikili bahislerde yaygın uygulanan bir yöntemdir. Örneğin bir karşılaşmada bahis için A takımına para yatıranlarla B takımına para yatıranların oranlarına bakılır. Bahis komisyon çıkartılarak bu oranda dağıtılır. #---------------------------------------------------------------------------------------------------------------------------- #---------------------------------------------------------------------------------------------------------------------------- 107. Ders - 08/03/2025 - Cumartesi #---------------------------------------------------------------------------------------------------------------------------- #---------------------------------------------------------------------------------------------------------------------------- İstatistiksel lojistik regresyonun anlaşılması biraz zor olabilmektedir. Biz burada bu yöntemin adım adım nasıl uygulandığını açıklayacağız. Lojistik regresyonda aslında yine bir doğru denkleminin bulunması hedeflenir. Yani bu bakımdan biraz doğrusal regresyona benzemektedir. Ancak belli bir noktanın x değerlerinin 1 olma olasılığı x değerlerinin bu doğru denklemine sokulmasıyla elde edilmez. Çünkü doğru denklemleri 0 ile 1 arasında bir değer vermemektedir. İşte belli bir noktaya ilişkin x değerlerinin 1 olma olasılığına dönüştürülmesi bu x değerlerinin doğru denklemine sokulup oradan elde edilen değerin sigmoid fonksiyonuna sokulmasıyla elde edilmektedir. Bizim BX + b biçiminde bir doğru denklemini bir biçimde elde etmiş olduğumuzu düşünelim. (Buradaki B ve X bir vektrödür dolayısıyla buradaki BX + b aslında N değişkenli genel bir doğru denklemini belirtmektedir.) İşte eğer bizim elimizde böyle bir doğru denklemi varsa bir noktaya ilişkin x değerlerinin 1 olma olasılığı şöyle elde edilecektir: p(x) = 1 / (1 + e^-(BX + b)) Bu aslında X noktasına ilişkin değerlerin sigmoid fonksiyonuna sokulmasıyla elde edilen değerdir. Sigmoid fonksiyonunun 0 ile 1 arasında bir değer verdiğini anımsayınız. Tabii buradaki asıl sorun böyle bir doğru denkleminin nasıl elde edileceğidir. Bunu izleyen paragraflarda açıklayacağız. Ancak yukarıdaki eşitlikte BX + b değerini (yani noktaya ilişkin x değerlerinin doğru denklemine sokulmasıyla elde edilen değeri) içler dışlar çarpımıyla yalnız bırakmaya çalışalım: p(x) * (1 + e^-(BX + b)) = 1 (1 + e^-(BX + b)) = 1 / p(x) e^-(BX + b) = (1 - p(x))/p(x) Her iki tarfın logaritmasını alalım: log(e^-(BX + b)) = log((1 - p(x))/p(x)) -(BX + b) = log((1 - p(x))/p(x)) BX + b = -log((1 - p(x))/p(x)) BX + b = log(p(x) / (1 - p(x))) Buradan ilginç bir sonuç çıkmaktadır. Eğer biz böyle bir doğru denklemi bulursak aslında noktaya ilişkin x değerlerini bu doğru denkleminde yerine koyduğumuzda "odds ratio" değerinin logaritmasını elde ederiz. İşte lojistik regresyonda "odds ratio" değerinin logaritmasına "logit" de denilmektedir. Aslında lojistik regresyondaki "log" önekinin kaynağı da buradaki logaritma işlemidir. #---------------------------------------------------------------------------------------------------------------------------- #---------------------------------------------------------------------------------------------------------------------------- Pekiyi BX + b doğru denklemi nasıl elde edilmektedir? Biz yukarıda bunun elde edilmiş olduğunu varsaydık. İşte bu doğru denklemi aslında toplam olasılıkların maksimizasyonu ya da bunun negatifinin minimizasyonu yoluyla oluşturulmaktadır. Sigmoid fonksiyonunu yeniden veriyoruz: p(x) = 1 / (1 + e^-(BX + b)) Biz belli bir noktaya ilişkin x değerlerini bu fonksiyona sokup bir p(x) değeri elde ediyoruz. Bu p(x) değerinin de gerçek y değeri olan 1 ya da 0'a ne kadar yakınsa bu doğru denkleminin o kadar iyi oluşturulduğunu söyleyebiliriz. İşte tersten gidersek bizim amacımız yukarıdaki sigmoid çıktısı ile gerçek değerler arasındaki farkı minimize edecek doğru denkleminin bulunmasıdır. Örneğin bir nokta için p(x) değeri 0.8 olsun bu noktanın da gerçek sınıfı 1 olsun. Bu durumda biz bu p(x) değerini daha fazla 1'e yaklaştırmaya çalışırız. Örneğin bir nokta için p(x) değeri 0.2 olsun. Bu noktanın da gerçek sınıfı 0 olsun. Bu durumda biz bu değeri daha fazla 0'a yaklaştırmak isteriz. Tabii burada bir noktaya dikkat etmek gerekir. Örneğin biz yukarıdaki sigmoid çıktısından 0.1 değeri elde etmiş olalım. Eğer bu noktanın gerçek y değeri 1 ise buradaki hata büyük olur ancak 0 ise buradaki hata küçük olur. O halde böyle bir amaç fonksiyonunu (ya da loss fonksiyonunu) minimize ederken bu durumu dikkate almalıyız. İşte minimize edilecek amaç fonksiyonu (yani loss fonksiyonu) aşağıdaki gibi oluşturulmaktadır: -(p(x)^y * (1 - p(x))^(1 - y)) Burada eğer y = 0 ise çarpımın sağ tarafı 1, y = 1 ise çarpımın sol tarafı 1 olur. Burada parantez içi ne kadar yükseltilirse elde edilen sınıf gerçek sınıfa o kadar yaklaşmaktadır. Parantezin başındaki negatif işlemi problemi maksimizasyon yerine minimizasyon haline getirmektedir. Üslü ifadelerden kurtulmak için ifadenin logaritmasını alabiliriz: -log((1 - p(x))^(1 - y) * p(x)^y) İki değerin çarpımının logaritması logaritmalarının toplamına eşittir: -(log((1 - p(x))^(1 - y)) + log(p(x)^y)) Logaritma işleminde üst başa düşürülebilir: -((1 - y) * log(1 - p(x)) + y * log(p(x))) Zaten bu da aslında daha görmüş olduğumuz "binary cross-entropy" loss fonksiyonu ile aynıdır. Tabii biz aslında burada tek bir noktanın değil bütün noktaların toplamını minimize etmeye çalışmaktayız. O halde aslında gerçek amaç fonksiyonu şu biçimde olacaktır: Her x için -∑[(1 - yi) * log(1 - p(xi)) + yi * log(p(xi))] Tabii bu minimizasyon işlemi kapalı biçimde yapılamaz. Ancak nümerik yöntemlerle (gradient descent ya da Newton-Raphson gibi yöntemlerle) yapılabilir. Bu tür optimizasyonların nasıl yapıldığı ileride ele alınacaktır. #---------------------------------------------------------------------------------------------------------------------------- #---------------------------------------------------------------------------------------------------------------------------- İstatistikte yukarıda belrttiğimiz BX + b doğru denkleminin elde edilmesi için kullanılan amaç fonksiyonuna "maximum likelihood" denilmektedir. İsminden de anlaşılacağı gibi "maximum likelihood" yönteminde amaç fonksiyonu maksimizasyon için oluşturulmaktadır. Halbuki biz yukarıda poblemi bir minimizasyon problemi olarak modelledik. İşte aslında "maximum likelihood" amaç fonksiyonu ile yukarıda oluşturduğumuz "binary cross-entropy" amaç fonksiyonu ters yönde aynı amaca hizmet etmektedir. Maximum likelihood amaç fonksiyonun dayandığı fikir veri kümesindeki satırların olasılıklarının çarpımının en büyüklenmesidir. Satırların istatistiksel olarak bağımsız olduğunu varsayarsak satırların sigmoid çıktılarının çarpımı aslında toplam veri kümesinin olasılığını vermektedir. İşte bu olasılığın maksimize edilmesi aslında kaybın minimize edilmesiyle aynı anlama gelmeketedir. Maximum likelihood amaç fonksiyonunu şöyle oluşturulabilir: ∏[p(x)^y * (1 - p(x))^(1-y)] Bu ifadenin logaritmasını alalım: ∑[y * log(p(x)) + (1 - y) * log(1 - p(x))] Buradan da görüldüğü gibi aslında maximum likelihood amaç fonksiyonu binary cross entropy-fonksiyonunun pozitiflisidir. Bir fonksiyonun negatifinin minimize edilmesiyle pozitifinin maksimize edilmesi aynı anlamdadır. #---------------------------------------------------------------------------------------------------------------------------- #---------------------------------------------------------------------------------------------------------------------------- Aslında istatistiksel lojistik regresyonun tek bir nöronda oluşan ve bizim "perceptron" dediğimiz sinir ağı birimiyle yakın bir ilişkisi vardır. Şöyle ki: Aslında aktivasyon fonksiyonu "sigmoid" olan loss fonksiyonu "binary cross-entropy" olan perceptron tamamen yukarıda açıkladığımız lojistik regresyon modelinin aynısıdır. #---------------------------------------------------------------------------------------------------------------------------- #---------------------------------------------------------------------------------------------------------------------------- Biz konuya girişte lojistik regresyon modelinin bir "doğrusal sınıflandırıcı (linear classifier)" olduğunu beelirtmiştik. Yani bu model aynı zamanda bir hyperplane ile iki sınıfı birbirinden ayrıştırmaya çalışmaktadır. Bunun ispatı şöyle yapılabilir. BX + b doğru denkleminin aynı zamanda "odds ratio" değerinin logaritmasını olduğunu belirtmiştik: BX + b = log(p(x) / (1 - p(x))) Burada p(x) = 0.5 alırsak eşitlik şu hale gelir: BX + b = log(1) BX + b = 0 Bu durumda aslında bizim elde ettiğimiz doğru denklemini 0'a eşitleyerek bir doğru (genel olarak hyperplane) çizersek aslında 0.5 olasılıkla noktaları ikiye ayırmış oluruz. Yukarıda kalanlar bir sınıf aşağıda kalanlar bir sınıf olur. Başka bir deyişle aslında lojistik regresyondan elde edilen doğru denklemi 0'a eşitlenirse bir doğrusal sınıflandırıcı haline gelmektedir. Örneğin X1 ve X2'den oluşan iki özellikli bir veri kümesi için ayırıcı doğru şöyle çizilecektir: B0 + B1x1 + B2x2 = 0 B2X2 = -B0 - B1X1 X2 = (-B0 - B1X1) / B2 Burada biz X1'e değer vererek X2 değerlerini elde edip doğruyu çizebiliriz. #---------------------------------------------------------------------------------------------------------------------------- #---------------------------------------------------------------------------------------------------------------------------- 108. Ders - 09/03/2025 - Pazar #---------------------------------------------------------------------------------------------------------------------------- #---------------------------------------------------------------------------------------------------------------------------- Loojistik regresyon işlemi için scikit-learn kütüphanesinde sklearn.linear_model modülünde LogisticRegression isimli bir sınıf bulunurulmuştur. 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='deprecated', 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 bir regülasyon yapılmaz, 'l1' geçilirse L1 regülasyonu (Lasso regülasyonu), 'l2' geçilirde L2 regülasyonu (Ridge regülasyonu) uygulanır. Eğer bu parametreye 'elasticnet' geçilirse Hem L1 regülasyonu hem de L2 regülasyonu (Elastic Net regülasyonu) uygulanmaktadır. Metodun C parametresi L1 ve L2 regülasyonundaki çarpanı ayarlamak için kullanılmaktadır. L1 ve L2 regülasyonunda da l1_ratio parametresi bu regülasyonlar arasındaki oranı belirtmektedir. Tabii uygulamacı genellikle tüm parametreleri default değerle geçerek nesneyi yaratır. Örneğin: lr = LogisticRegression() 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. Yani predict fonksiyonu verdiğimiz noktanın x değerlerini yukarıda belirttiğimiz sigmoid fonksiyonuna sokar, eğer bu değeri > 0.5 ise noktanın sınıfını 1 olarak < 0.5 ise 0 olarak belirler. Sınıfın predict_proba isimli metodu bize her noktanın sınıfsal olasıklarını vermektedir. Yani bu metot bize noktaya ilişkin x değerlerinin sigmoid fonksiyonuna sokulmasından elde edilen değeri verir. Ancak bu metot her noktanın 0 olma olasılığı ve 1 olma olasılığını iki sütun halinde vermektedir. Dolayısıyla bu metodun verdiği matrisin 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 + B1x1 + B2x2 + ... + Bnxn 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.eklemesinin yapılması gerekir. Çünkü nümerik optimizasyon problmeleri çözülürken sütunlar arasındaki skala farklılıkları nümerik çözümü olumsuz etkilemektedir. Genel olarak nümerik optimizasyonun yapıldığı durumlarda özellik ölçeklemesi uygulanmalıdır. #---------------------------------------------------------------------------------------------------------------------------- #---------------------------------------------------------------------------------------------------------------------------- Aşağıda iki boyutlu X1 ve X2 özelliklerinden oluşan "logistic-points.csv" dosyasındaki noktalar üzerinde lojistik regresyon uygulaayan bir örnek verilmiştir. Bu örnekte önce StandardScaler işlemi yapılıp oradan elde edilen noktalar LogisticRegression sınıfına verilmiştir. predict metodu ile kestirim de yapılmıştır. Buradaki 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 -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 #---------------------------------------------------------------------------------------------------------------------------- import numpy as np from sklearn.preprocessing import StandardScaler 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() ss = StandardScaler() scaled_dataset_x = ss.fit_transform(dataset_x) scaled_dataset_x = np.append(scaled_dataset_x, np.ones((dataset_x.shape[0], 1)), axis=1) import matplotlib.pyplot as plt plt.figure(figsize=(8, 6)) 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, niter=50000): b = np.zeros((dataset_x.shape[1], 1)) for k in range(niter): h = sigmoid(dataset_x @ b) error = h - dataset_y grad = dataset_x.T @ error b = b - learning_rate * grad return b b = gradient_descent_logistic(scaled_dataset_x, dataset_y.reshape(-1, 1)) x1 = np.linspace(-5, 5, 1000) x2 = (-b[2] - b[0] * x1) / b[1] points = np.vstack((x1, x2)).T transformed_points = ss.inverse_transform(points) 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(transformed_points[:, 0], transformed_points[:, 1], 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: - Yukarıda da belirttiğimiz gibi aslında lojistik regresyon aktivasyon fonksiyonu sigmoid olan ve loss fonksiyonu "binary cross-entropy" olan perceptron ile eşdeğerdir. Tabii biz burada sinir ağı demekle çok nörondan oluşan en az iki katmanlı ağları kastediyoruz. - Çeşitli yöntemlerin hangisinin daha iyi sonuç vereceğini verilerin dağılımını bilmeden öngörmek çok zor bazen 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 "otomatik makine öğrenmesi araçları" aslında bunu yapmaktadır. - İstatistiksel lojistik regresyon yukarıda da belirttiğimiz 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ğrusal 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. #---------------------------------------------------------------------------------------------------------------------------- #---------------------------------------------------------------------------------------------------------------------------- Tabii biz StandardScaler ve LogisticRegression nesnelerini bir boru hattına da verebiliriz. Ancak her durumda boru hattı istediğimiz esnekliği sunmamaktadır. Örneğin burada boru hattı grafik çizimlerinde bize zorluk çıkartacaktır. Aşağıda böyle bir hattı örneği verilmiştir. #---------------------------------------------------------------------------------------------------------------------------- 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() 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() from sklearn.preprocessing import StandardScaler from sklearn.linear_model import LogisticRegression from sklearn.pipeline import Pipeline pl = Pipeline([('scaling', StandardScaler()), ('logistic-regression', LogisticRegression())]) pl.fit(dataset_x, dataset_y) predict_data = [(-0.733928, 9.098687) , (0-3.642001, -1.618087), (0.556921, 8.294984)] predict_result = pl.predict(predict_data) print(predict_result) #---------------------------------------------------------------------------------------------------------------------------- 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 from sklearn.metrics import accuracy_score from sklearn.naive_bayes import GaussianNB from tensorflow.keras import Sequential from tensorflow.keras.layers import Dense 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) score = accuracy_score(test_dataset_y, predict_result) print(f'LogisticRegression accuracy score: {score}') # Naive Bayes Solution 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 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}') #---------------------------------------------------------------------------------------------------------------------------- 109. Ders - 15/03/2025 - Cumartesi #---------------------------------------------------------------------------------------------------------------------------- #---------------------------------------------------------------------------------------------------------------------------- Biz yukarıda iki sınıflı (binary) lojistik regresyon üzerinde durduk. Zaten istatistikte lojistik regresyon denildiğinde default olarak iki sınıflı lojistik regresyon anlaşılmaktadır. Çok sınıflı (multinomial/multiclass) lojistik regresyon problemleri de aslında genel yapı itibari ile iki sınıflı lojistik regresyon problemlerine benzemektedir. Çok sınıflı lojistik regresyon problemlerinin çözümü için üç farklı yöntem kullanılabilmektedir. Bunlardan birine "OvR (One versus Rest)" yöntemi denir. Bu yöntemde her sınıfı diğerlerinden ayırmak için bir doğru denklemi elde edilmeye çalışılır. Dolayısıyla K tane sınıf söz konusuysa K tane doğru denklemi elde edilecektir. İstatistikte daha çok bu yöntem kullanılmaktadır. Böylece sanki çok sınıflı lojistik regresyon birden fazla iki sınıflı lojistik regresyon gibi ele alınmaktadır. Örneğin bu yöntemde C1, C2 ve C3 biçiminde 3 sınıflı lojistik resgresyon probleminde C1'i diğerlerinden ayıran, C2'yi diğerlerinden ayıran ve C3'ü diğerlerinden ayıran üç farklı iki sınıflı lojistik regresyon uygulanmaktadır. Dolayısıyla üç farklı doğru denklemi elde edilmektedir. Burada kestirim, kestirilecek noktanın üç ayrı iki sınıflı lojistik regresyona sokulması ve hangi olasılık değeri yüksekse o sınıfa atanması biçiminde yapılmaktadır. Ancak bu yöntemin bazı dezavantjları da vardır. Çok sınıflı lojistik regresyon problemlerinin çözümü için kullanılan ikinci yönteme "OvO (One versus One)" denilmektedir. Burada her sınıf her sınıfla ikili lojistik regresyona sokulur. Kestirim işlemi de oylama (voting) yoluyla yapılır. Örneğin elimizde C1, C2, C3, C4 biçiminde dört sınıflı bir veri kümesi olsun. Biz burada her sınıfı her sınıfla ikili jojistik regresyona sokarız: C1 ile C2 C1 ile C3 C1 ile C4 C2 ile C3 C2 ile C4 C3 ile C4 Kestirim sırasında kestirilecek noktayı buradaki tüm regresyonlara sokup oylama yaparız. Bu yöntemde aşağıdaki gibi bir sonucun elde edidiğini varsayalım: C1 ile C2 => C2 C1 ile C3 => C1 C1 ile C4 => C1 C2 ile C3 => C3 C2 ile C4 => C2 C3 ile C4 => C4 Burada oylamada C1 en iyi puanı elde ettiği için noktayı C1 sınıfına atarız. Bu yöntemde sınıf sayısı K olmak üzere oluşturulacak ikili lojistik regresyon sayısının C(K, 2) olduğuna dikkat ediniz. Örneğin K = 4 olduğu durumda C(4, 2) = 6 farklı ikili lojistik regresyon oluşturulacaktır. Pekiyi bu yöntemde aynı oyu alan birden fazla sınıf olursa ne seçim nasıl yapılacaktır? İşte burada seçim birkaç biçimde yapılabilir. Aynı oyu alan sınıflardan biri rastgele seçilebilir, aynı oyu alan sınıfların oylamadan elde ettikleri toplam olasılığa (sigmoid fonksiyonlarının verdiği olasılıkların toplamına) bakılabilir ya da aynı oyu alan sınıflardan birisine öncelik tanınabilir. Üçüncü yönteme "multinomial" yöntem de denilmektedir. Bu yöntemde sınıf sayısı kadar doğru denklemi ayrı ayrı oluşturmak yerine tek hamlede softmax fonksiyonu uygulanarak oluşturulmaktadır. Softmax fonksiyonunu anımsatmak istiyoruz: Softmax(zi) = e^(zi) / Σ(e^(zj)) Burada zi doğru denkleminden elde edilen değeri belirtmektedir. Bu değerlerin e tabanına göre kuvvetlerinin alındığını görüyorsunuz. Paydadaki toplam da tüm doğru denklemlerinden elde edilen değerlerin üstel toplamlarını belirtmektedir. Örneğin üç sınıflı bir lojistik regresyon probleminde üç ayrı doğru denklemi beraber bulunmaya çalışılır. Buradaki zi ve zj değerleri x değerlerinin bu doğru denklemlerine sokulmasıyla elde edilen değerleri temsil etmektedir. Örneğin biz C1, C2 ve C3 biçiminde üç sınıflı iki özellikli bir lojistik regresyonda doğru denklemlerini aşağıdaki gibi temsil etmiş olalım: z1 ---> B10 + B11X1 + B12X2 z2 ---> B20 + B21X1 + B22X2 z3 ---> B30 + B31X1 + B32X2 Elimizde bir noktaya ilişkin x değerleri olsun. Biz de yukarıda softmax fonksiyonu ile bu x değerlerinin C1 sınıfına, C2 sınıfına ve C3 sınıfına ilişkin olma olasılıklarını elde edebiliriz: x noktasının C1 olma olasılığı = e^(z1) / (e^(z1) + e^(z2) + e^(z3)) x noktasının C2 olma olasılığı = e^(z2) / (e^(z1) + e^(z2) + e^(z3)) x noktasının C3 olma olasılığı = e^(z3) / (e^(z1) + e^(z2) + e^(z3)) Burada elde edilen bu üç olasılığın toplamının 1 olduğuna dikkat ediniz. Pekiyi buradaki doğru denklemleri nasıl elde edilmektedir? Anımsayacağınız gibi biz 2 sınıflı lojistik regresyon problemlerinde "binary cross-entropy" isimli amaç fonksiyonunu (loss fonkisyonunu) kullandık. Bu da zaten "maximum likelihood" amaç fonksiyonunun negatifiydi. Pekiyi örneğin üç sınıflı bir lojistik regresyon probleminde biz nasıl bir amaç fonksiyonu kullanmalıyız? İşte yapay sinir ağları konusunda da gördüğümz gibi buradaki amaç fonksiyonuna (loss fonksiyonuna) "categorical cross-entropy" denilmektedir. "categorical cross-entropy" fonksiyonu yeniden anımsatmak istiyoruz: L = -∑(c=1 to C) y_ic × log(p_ic) Burada p_ic değeri softmax fonksiyonundan elde edilen değerdir. Dolayısıyla aslında bu loss fonksiyonu eşliğinde doğru denklemleri güncellenmektedir. Buradaki y_i değerleri ilgili satırın gerçek değerlerini belirtmektedir. Ancak bu değer one-hot-encoding biçimindedir. Dolayısıyla buradaki toplamda yalnızca tek bir sınıf için değer oluşacaktır. Örneğin üç sınıflı bir lojistik regresyonda bir satırın gerçek sınıfı 2 olsun. Bu durumda y_ic aslında 0 1 0 biçimindedir. Dolayısıyla bu toplamdan yalnızca -log(p_i2) elde edilir. Bu da ikinci sınıfa ilişkin softmak değerinin logaritmasının negatif değeridir. Burada örneğin üç sınıflı lojistik regresyonda eğitim sonucunda üç doğru denkleminin oluşacağına dikkat ediniz. Bu doğru denklemlerinin geometrik yorumlaması zor yapılamayabilir. Çünkü buradaki doğru denklemleri sınıfları ayıran bir karakterde değildir. #---------------------------------------------------------------------------------------------------------------------------- #---------------------------------------------------------------------------------------------------------------------------- scikit-learn kütüphanesindeki LogisticRegression sınıfı çok sınıflı lojistik regresyonlarda da kullanılabilmektedir. Sınıfın __init__ metodunu yeniden anımsatmak istiyoruz: 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='deprecated', verbose=0, warm_start=False, n_jobs=None, l1_ratio=None) Metotta lojistik regresyonun iki sınıflı mı çok sınıflı mı olduğuna yönelik bir parametrenin olmadığına dikkat ediniz. Çok sınıflı lojistik regresyonda fit işlemi sırasında fit metodu zaten y değerlerinden hareketle sınıf sayısını kendisi elde edebilmektedir. Pekiyi sınıf yukarıda açıkladığımız üç yöntemden hangisini kullanmaktadır? İşte bir süre önceye kadar çok sınıflı lojistik regresyon için sınıfın kullanacağı yöntem metodun multi_class parametresiyle nispeten ayarlanabiliyordu. Ancak gördüğünüz gibi bu parametre "deprecated" yapılmış durumdadır. Yani kütüphanenin sonraki versyonlarında bu parametre tümden kaldırılacaktır. Bu parametreye üç değer girilebilmektedir: auto ovr mutinomial "ovr" yukarıdaki açıkladığımız OvR yöntemini, "multinomial" ise multinomial yöntemin kullanılacağı anlamına gelmektedir. auto iki sınıf için "binary lojistik regresyon", ikiden fazla sınıf için "multinomial" anlamına gelir. Bu parametrenin halen default değeri "auto" biçimdedir. Tabii bu parametre kaldırılınca default durum çok sınıflı lojistik regreasyon için "multinomial" olacaktır. Pekiyi biz OvR ya da OvO kullanmak istersek ne yapmalıyız? İşte bunun için sklearn.multiclass modülünde iki ayrı adaptör sınıf da bulundurulmuştur: OneVsOneClassifier OneVsRestClassifier Bu iki sınıf aslında ikili sınıflandırıcaları parametre olarak alıp işlemini yapmaktadır. Biz bu sınıfların estimator parametrelerine konu bağlamında LogisticRegression nesnesi vermeliyiz. Örneğin: ovo = OneVsOneClassifier(LogisticRegression()) ovr = OneVsRestClassifier(LogisticRegression()) #---------------------------------------------------------------------------------------------------------------------------- #---------------------------------------------------------------------------------------------------------------------------- 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 genel olarak aynı sonuçlar elde edilmiştir. Bunun nedeni şüphesiz kümlerin birbirlerinden oldukça ayrık durumda olması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) 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 from sklearn.metrics import accuracy_score lr = LogisticRegression() lr.fit(scaled_training_dataset_x, training_dataset_y) predict_result = lr.predict(scaled_test_dataset_x) score = accuracy_score(test_dataset_y, predict_result) print(f'Multinomial LogisticRegression score: {score}') # Neural Net Solution from sklearn.preprocessing import OneHotEncoder ohe = OneHotEncoder(sparse_output=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) 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ı zordur. Aşağıdaki programda MNIST veri kümesine hem çok sınıflı lojistik regresyon hem de sinir ağı uygulanmıştır. Buradan accuracy değerleri şöyle elde edilmiştir: LogisticRegresson accuracy score: 0.9203 Simple Neural Net accuracy score: 0.9808 Görüldüğü gibi resim tanıma gibi işlemlerde yapay sinir ağları alternatif yöntemlere göre çok daha iyi sonuç vermektedir. Tabii biz burada yapay sinir ağında derin bir ağ kullanmadık. Önceden eğitilmiş RestNet gibi modeller çok daha iyi sonucun elde edilmesine olanak sağlamaktadır. #---------------------------------------------------------------------------------------------------------------------------- from tensorflow.keras.datasets import mnist (training_dataset_x, training_dataset_y), (test_dataset_x, test_dataset_y) = mnist.load_data() training_dataset_x = training_dataset_x.reshape(-1, 28 * 28) test_dataset_x = test_dataset_x.reshape(-1, 28 * 28) # one hot encoding for y data from tensorflow.keras.utils import to_categorical from sklearn.metrics import accuracy_score ohe_training_dataset_y = to_categorical(training_dataset_y) ohe_test_dataset_y = to_categorical(test_dataset_y) # minmax scaling scaled_training_dataset_x = training_dataset_x / 255 scaled_test_dataset_x = test_dataset_x / 255 # LogisticRegression Solution 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) score = accuracy_score(test_dataset_y, predict_result) print(f'LogisticRegresson accuracy score: {score}') # Simple Neural Net Solution 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}') #---------------------------------------------------------------------------------------------------------------------------- Saklı katmanı tek nörondan oluşan çıktı katmanı 4 nörondan oluşan, çıktı aktivasyon fonksiyonu "softmax" ve loss fonksiyonu "categorical cross-entropy" olan bir sinir ağı modeli ile "multinomial" yöntem kullanan lojistik regresyon modeli çalışma biçimi olarak birbirine çok benzemektedir. Çünkü her iki modelde de aslında aynı amaç fonksiyonu nümerik yöntemlerle minimize edilmeye çalışılmaktadır. Tabii lojistik regresyonun bize aynı zamanda doğru denklemi verdeğine de dikkat ediniz. #---------------------------------------------------------------------------------------------------------------------------- #---------------------------------------------------------------------------------------------------------------------------- Çok sınıflı lojistik regresyonlarda örneğin özellik sayısı iki ise biz regresyon doğrularını çizerek belli bir noktayı gözle kestirebilit miyiz? İşte "multinomial" yöntemde gözle kestirim yapmak mümkün olmayabilir. OvR yönteminde gözle kestirim yapmak nispteten daha kolaydır. Çünkü bu yöntemde sınıf sayısı K olmak üzere K tane regresyon doğrusu bazı uzayı çeşitl bölgeler ayırmaktadır. Her sınıfın bir bölgesi olur. Ancak bazı bölgeler birden fazla sınııfn içerisine girebilmektedir. İşte bu bölgelerdeki noktaların hangi sınıfa ilişkin olduğu regresyon doğrularına olan uzaklıkla belirlenebilir. Bir nokta hangi regresyon doğrusuna daha uzaksa o sınıfa girmektedir. Aşağıdaki örnekte 3 sınıflı bir veri kğmesi oluşturup "multinomial" ve "OvR" kullanılarak lojistik regresyon doğruları ç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=2, random_state=100) colors = ['red', 'green', 'blue'] import matplotlib.pyplot as plt plt.figure(figsize=(8, 6)) 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)) lr = LogisticRegression(multi_class='ovr') 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() #---------------------------------------------------------------------------------------------------------------------------- 110. Ders - 16/03/2025 - Pazar #---------------------------------------------------------------------------------------------------------------------------- #---------------------------------------------------------------------------------------------------------------------------- Makine öğrenmesinde sık kullanılan doğrusal sınıflandırıcılardan (linear classifiers) biri de "Destek Vektör Mekineleri (Support Vector Machine)" denilen sınıflandırıcıdır. Bu yöntem 1992'de ilk kez ortaya konmuş ve zaman içerisinde bazı yeteneklerinden dolayı gittikçe popülerlik kazanmıştır. Biz kursumuzda "Destek Vektör Makineleri" yerine kısaca "SVM (Support Vector Machine)" de diyeceğiz. Aslında temel olarak ele alındığında SVM'ler istatistiksel lojistik regresyonlara benzemektedir. SVM'ler temel olarak sınıflandırma problemleri için kullanılıyor olsa da regresyon problemleri için de kullanılabilmektedir. SVM'lerin regresyon problemleri için kullanılan biçimlerine SVR (Support Vector Regression) da denilmektedir. İstatistiksel lojistik regresyonda gerçek sınıflarla regresyon doğrusundan elde edilen sınıflar arasındaki farklar minimize edilmeye çalışılmaktadır. (Ya da bunun tersi maksimize edilmeye çalışılmaktadır ki buna "maximum likelihood" denildiğini anımsayınız.) Oysa SVM'lerin dayandığı fikir daha farklıdır. SVM'lerde de yine iki sınıfı ayıran bir hyperplane elde edilmeye çalışılır. Ancak bu hyperplane kendisine en yakın farklı sınıflardaki noktalar arasındaki toplam uzaklığı en büyüklemek amacıyla olşturulmaktadır. Burada noktanın SVM heyperplane'ine uzaklığı için "dikme uzaklığı" kullanlmaktadır. İki kümeyi ayırma iddiasında olan sonsuz sayıda hyperplane oluşturulabilir. 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 (destek vektörlerinin) hyperplane'a olan dikme uzaklıklarının toplamına "marjin (margin)" denilmektedir. Amaç bu marjinin en yüksek olduğu hyperplane'nin elde edilmesidir. #---------------------------------------------------------------------------------------------------------------------------- #---------------------------------------------------------------------------------------------------------------------------- SVM'lerin matematiksel temeli optimizasyon bağlamında biraz karışıktır. Bir noktanın bir doğruya dikme uzaklığı şöyledir: |w·x + b| / ||w|| Burada w vektörü x değerlerinin katsayılarını belirtmektedir. ||w|| ise bu katsayaların karelerinin toplamının karekökünü belirtir. Buradaki doğru denklemi w·x + b = 0 biçimindedir. Örneğin iki boyutlu kartezyen koordinat sisteminde doğru denklemini w1x1 + w2x2 + b = 0 biçiminde de ifade edebiliriz. Bu durumda ||w|| ifadesi de sqrt(w1^2 + w2^2) biçiminde olur. Bu durumda iki noktanın uygun doğruya dikme uzaklıkları şu hale gelir: d₁ = |w·x1 + b| / ||w|| d₂ = |w·x2 + b| / ||w|| Burada x1 noktalardan birini x2 ise diğerini temsil etmektedir. Noktalardan biri doğrunun bir tarafında diğeri de diğer tarafında kalacaktır. Dolayısıyla doğru denkleminin kısıtları şöyle olur: yi(w·xi + b) ≥ 1, i = 1, 2, ..., n Burada eğitim sırasında sınıflardan birini +1, diğerine -1 olarak etiketlendirdiğimizi varsayıyoruz. Bu kısıttaki yi değeri eğitim sırasında -1 ya da +1 olabilmektedir. Bu durumda minimal olarak karar sınırında yukarıdaki uzaklıklar şu hale gelecektir: d₁ = |w·x1 + b| / ||w|| = 1 / ||w|| d₂ = |w·x2 + b| / ||w|| = 1 / ||w|| Böylece maksimize edilecek amaç fonksiyonu şöyle olşturulur: Zmax = 2 / ||w|| Burada payda küçültülürse kesir büyütülmüş olur. Bu durumda optimizasyon aslında paydanın küçültülmesi biçimine dönüştürülebilir. Paydanın kısıtlar altında minimize edilmesi için "lagrange çarpanları" denilen bir yöntemden faydalanılmaktadır. Ancak çözüm nihayetinde nümerik optimizasyon yöntemleriyle (yani örneğin "gradient descent" ya da "Newton-Raphson" gibi yöntemlerle) yapılabilir. Sonuç olarak bu minimizasyondan probleminden w değerleri elde edielcektir. Bu w değerleri (b (bias) değerinin buna dahil edilmediğine dikkat ediniz) aslında doğru denkleminin katsayı değerlerini belirtmektedir. Aslında SVM'de destek vektörleri iki sınıftan bir tane yani toplamda 2 tane olmak zorunda değildir. Daha fazla destek vektörü kullanılarak daha iyi marjinler elde edilebilmektedir. Eğer noktalar tamamen doğrusal olarak ayrıştırılabilir biçimdeyse iki destek vektörü yetebilmektedir. Ancak noktalar tamamen doğrusal olarak ayrıştırılabilir değilse optimizasyonda birden fazla destek verktörü kullanılarak daha iyi bir destek vektör doğrusu (hyperplane) elde edilebilmektedir. Genellikle bu tür algoritmalar bu destek vektörleerinin sayısı ya da marjini üzerinde etkili olabilecek hyper parametreler bulundurmaktadır. Destek vektörlerinin sayısı kernel trick" uygulandığında da artabilmektedir. #---------------------------------------------------------------------------------------------------------------------------- #---------------------------------------------------------------------------------------------------------------------------- Noktalar doğrusal olarak ayrıştırılabilir (linearly separable) 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. Büyük ölçüde doğrusal olarak ayrıştırılabilir veri kümelerinde lojistik regresyon ile SVM yöntemleri karşılaştırıldığında şunlar söylenebilir: - Lojistik regresyonda overfitting oluşma riski daha fazladır. SVM bu bakımdan daha dirençlidir. - Lojistik regresyon SVM yöntemine göre daha hızlı olma eğilimindedir. Özellikle veri kümesi büyüdüğünde ve özellik sayısı arttığında SVM lojistik regresyona göre daha yavaş olma eğilimindedir. - Lojistik regresyon bize bir olasılık değeri vermektedir. Halbuki SVM'den böyle bir olasılık değeri elde edilmemektedir. #---------------------------------------------------------------------------------------------------------------------------- #---------------------------------------------------------------------------------------------------------------------------- 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. Örneğin iki boyutlu uzayda doğrusal olarak ayrıştırılamayan noktalar bir projeksiyonla üç boyutlu uzayda doğrusal olarak ayrıştırılabilir hale getirilebilmektedir. Burada bu projeksiyonu yapan fonksiyona "kernel fonksiyonu" denilmektedir. Kernel trick noktaları gerçek anlamda transpoze etmeden transpoze edilmiş gibi işlemlere sokulmasını sağlamaktadır. Kernel değiştirme işleminin nasıl yapıldığının matematiksel açıklaması biraz ayrıntılıdır. Biz burada bunun nasıl yapıldığı üzerinde durmayacağız. İstatistiksel lojistik regresyonda böyle bir "kernel trick" yapılamamaktadır. Daha önceden de belirttiğimiz gibi nümerik optimizasyonların söz konusu olduğu durumlarda genel olarak özellik ölçeklemesi uygulanmalıdır. Dolayısıyla SVM işlemlerinin öncesinde eğer sütunlarda skala farklılıkları varsa özellik ölçeklemesinin uygulanması gerekir. #---------------------------------------------------------------------------------------------------------------------------- #---------------------------------------------------------------------------------------------------------------------------- Destek vektör makineleri için scikit-learn kütüphanesinde sklearn.svm modülü içerisinde SVC (Support Vector Classifier) 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 fonksiyonları yöntemleri uygulanabilmektedir. scikit-learn şu kernel fonksiyonlarını 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. Örneğin veri kümesi küresel biçimde iç içe geçmiş noktalardan oluşsa bile bu "rbf" kernel bu noktaları daha yüksek boyutlu uzayda birbirinden ayırabilmektedir. "linear" kernel istatistiksel lojistik regresyonda olduğu gibi kernel kullanmadan ayrıştırma yapar. "poly" kernel polinomsal transformasyon yapmaktadır. Başka bir deyişle "poly" kernel polinomsal bir fonksiyonla boyut yükseltmesi yaparak ayrıştırma sağlar. "poly" kernel'daki yüksek boyut polinomun derecesiyle artırılabilmektedir. Metottaki degree parametresi "poly" kernel kullanıldığında polinomun derecesi belirtmektedir. "rbf" ekrnel için bu parametre dikkate alınmamaktadır. Metodun probablity parametresi default durumda False biçimdedir. Bu parametre True yapılırsa noktaların ilgili sınıfta olma olasılıkları da hesaplanmaktadır. Bu parametre True yapılmazsa predict_proba metodu uygulanamamaktadır. SVC nesnesi 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ğrudan 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 a0 + a1x1 + a2x2 + ... + anxn = 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ğıda örnekte "logistic-points.csv" dosyası ile temsil ettiğimiz noktalar üzerinde "linear kernel" kullanılarak SVM uygulaması yapımıştır. Elde edilen doğru ve destek vektörleri grafikle gösterilmiştir. "logistic-points.csv" dosyasının içeriğini yukarıda daha önce vermişik. #---------------------------------------------------------------------------------------------------------------------------- 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() import matplotlib.pyplot as plt plt.figure(figsize=(9, 7)) 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() from sklearn.preprocessing import StandardScaler ss = StandardScaler() scaled_dataset_x = ss.fit_transform(dataset_x) from sklearn.svm import SVC svc = SVC(kernel='linear') svc.fit(scaled_dataset_x, dataset_y) x = np.linspace(-3, 2, 1000) y = -(svc.intercept_ + svc.coef_[0, 0] * x) / svc.coef_[0, 1] points = np.vstack((x, x)).T transformed_points = ss.inverse_transform(points) support_x = dataset_x[svc.support_] support_y = dataset_y[svc.support_] import matplotlib.pyplot as plt plt.figure(figsize=(9, 7)) 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(transformed_points[:, 0], transformed_points[:, 1], color='red') plt.scatter(support_x[support_y == 0, 0], support_x[support_y == 0, 1], marker='o', color='red') plt.scatter(support_x[support_y == 1, 0], support_x[support_y == 1, 1], marker='^', color='red') plt.legend(['Class-1', 'Class-2', 'Support Vector Line', 'Support Vectors Class1', 'Support Vectors Class2']) plt.show() print(svc.support_) print(svc.support_vectors_) predict_data = [(-0.733928, 9.098687) , (0-3.642001, -1.618087), (0.556921, 8.294984)] scaled_predict_data = ss.transform(predict_data) predict_result = svc.predict(scaled_predict_data) print(predict_result) #---------------------------------------------------------------------------------------------------------------------------- 111. Ders - 22/03/2025 - Cumartesi #---------------------------------------------------------------------------------------------------------------------------- #---------------------------------------------------------------------------------------------------------------------------- Aşağıdaki örnekte daha önce kullanmış olduğumuz "logistic-points.csv" dosyasındaki noktalarla "linear kernel" 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. Zaten çizieln doğruları da gözle incelediğinizde SVC'nin "linear kernel" kullanılmış olsa da noktaları ayrıştırmak için daha iyi bir doğru elde ettiğini göreceksiniz. #---------------------------------------------------------------------------------------------------------------------------- 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() 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=(10, 6)) 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.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. Burada da yine "linear kernel" kullanılan SVC'nin lojistik regresyondan çok az daha iyi performans gösterdiğine dikkat ediniz. #---------------------------------------------------------------------------------------------------------------------------- import numpy as np from sklearn.datasets import make_blobs dataset_x, dataset_y = make_blobs(n_samples=100, n_features=2, centers=2, cluster_std=2, random_state=1234) 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=(10, 6)) 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.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 noktalarda lojistik regresyon ve "linear kernel" ile destek vektör makineleri başarısız olmaktadır Aşağıdaki örnekte iç içe dairesel noktalar make_cirles fonksiyonuyla oluşturulmuş sonra bu noktalar üzerinde SVC "linear kernel" ve LogisticRegression işlemleri uygulanmıştır. Buradaki accuracy değerleri her iki yöntemde de %50 elde edilmiştir. #---------------------------------------------------------------------------------------------------------------------------- import numpy as np from sklearn.datasets import make_circles dataset_x, dataset_y = make_circles(n_samples=100, noise=0.01) 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=(10, 6)) 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.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}') #---------------------------------------------------------------------------------------------------------------------------- Yukarıda da belirttiğimiz gibi SVC'nin lojistik regresyona göre en önemli üstünlüğü kernel değiştirerek doğrusal olarak ayrıştırılamayan noktaları özellik yükseltmesi ile doğrusal olarak ayrıştırılabilir hale getirmesidir. Yukarıda vermiş olduğumuz örneği bu kez SVC'de "linear kernel" yerine "rbf kernel" kullanarak aşağıda yeniden veriyoruz. Burada artık lojistik regresyonun performansı %50 iken, SVC'nin performası %100'e çıkmaktadır. #---------------------------------------------------------------------------------------------------------------------------- from sklearn.datasets import make_circles dataset_x, dataset_y = make_circles(n_samples=100, noise=0.01) 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=(10, 6)) 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 kernel seçilebilir. Ancak genellikle bu dağılım da bilinmemektedir. Bu durumda noktalar hakkında hiçbir bilgimiz yoksa "rbf kernel'ı (radial kernel)" seçebiliriz. Zaten SVC sınıfında "rbf" kernel default durumdur. "rbf kernel" doğrusal olarak ayrıştırılabilir noktalar söz konusu olduğunda da yeterli performansı gösterebilmektedir. Tabii eğer noktalar doğrusal olarak ayrıştırılabilir durumdaysa "rbf kernel" yerine "linear kernel" daha iyi bir performans gösterebilecektir. SVC yöntemini "rbf kernel" ile kullandığımızda hemen her zaman lojistik regresyondan elde ettiğimiz sonuçlardan daha iyi ya da onunla yaklaşık eşdeğer sonuç elde ederiz. 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 bilimcisinin kümesine ilişkin alternatif yöntemleri deneyip en iyi sonucu veren yöntemi tercih etmesi önerilmektedir. #---------------------------------------------------------------------------------------------------------------------------- #---------------------------------------------------------------------------------------------------------------------------- SVM tıpkı lojistik regresyonda olduğu gibi temelde iki sınıflı sınıflandırma problemleri için uygulanan bir yöntemdir. Ancak lojistik regresyonda gördüğümüz gibi çok sınıflı sınıflandırma problemleri iki sınıflı sınıflandırma problemlerine indirgenerek SVM ile de çözülebilmektedir. Bunun için yine lojistik regresyonda gördüğümüz "OvR (One versus Rest)" ve "OvO (One versus One)" yöntemleri kullanılmaktadır. Bu ayarlama SVC sınıfının __init__ metodundaki decision_function_shape parametresi ile ayarlanmaktadır. Bu parametrenin default değeri "ovr (one versus rest)" biçimindedir. Aşağıdaki örnekte make_blobs fonksiyonu ile iki özelliğe sahip üç sınıflı rastgele noktalar üretilip bu noktalar üzerinde "rbf kernel" ile SVC ve lojistik regresyon yöntemleri uygulanmıştır. Elde edilen sonuçlar şöyledir: SVC score: 0.78 Logistic score: 0.75 #---------------------------------------------------------------------------------------------------------------------------- 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}') #---------------------------------------------------------------------------------------------------------------------------- Aşağıdaki örnekte MNIST veri kümesi üzerinde lojistik regresyon, yapay sinir ağları ve destek vektör makineleri yöntemleri uygulanmıştır. Burada en iyi sonuç yapay sinir ağlarıyla elde edilmiştir. Destek vektör makinelerinin "rbf kernel" ile kullanımı lojistik regresyona göre daha iyi performans göstermiştir. Aşağıda yaptığımız deneme için accuracy skorlarını görüyorsunuz: LogisticRegression accuracy: 0.9195 Neural Net accuracy: 0.9798 SVC accuracy: 0.9792 #---------------------------------------------------------------------------------------------------------------------------- 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 Input, Dense model = Sequential(name='MNIST') model.add(Input((training_dataset_x.shape[1],))) model.add(Dense(256, activation='relu', name='Hidden-1')) model.add(Dense(128, activation='relu', name='Hidden-2')) model.add(Dense(10, activation='softmax', name='Output')) model.summary() model.compile(optimizer='rmsprop', loss='categorical_crossentropy', metrics=['categorical_accuracy']) hist = model.fit(training_dataset_x, ohe_training_dataset_y, epochs=20, batch_size=32) 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}') #---------------------------------------------------------------------------------------------------------------------------- Aşağıdaki örnekte "breast cancer" (meme kanseri)" veri kümesi üzerinde çeşitli yöntemler denenip bunlar karşılaştırılmıştır. Denenen yöntemler şunlardır: - "rbf kernel" ile SVC - Lojistik Regresyon - Naive Bayes - Sinir Ağı Modeli Elde edilen sonuçlar şöyledir: SVC accuracy score: 0.956140350877193 LogisticRegression accuracy score: 0.9649122807017544 GaussianNB accuracy score: 0.9210526315789473 NeuralNet accuracy score: 0.9649122807017544 Burada lojistik regresyon ve sinir ağı modeli aynı sonucu vermiştir ve bu sonuç diğerlerinden daha iyidir. Sonuçları şöyle özetleyebiliriz: Lojistik Regresyon = Sinir Ağı > SVC ("rbf" kernel) > Naive Bayes Pekiyi neden lojistik regresyon modeli SVC modelinden daha iyi sonuç vermiştir? Bunun nedeni noktaların doğrusal olarak ayrıştırılabilir olmasından kaynaklanmaktadır. Yukarıda da belirttiğimiz gibi böyle bir bilgi eşliğinde SVC'yi "linear kernel" ile kullanmak daha iyi bir sonucun elde edilmesine yol açacaktır. SVC "linear kernel" ile kullanıldığında ş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.9736842105263158 Buradaki sonucu şöyle özetleyebiliriz: Sinir Ağı > Lojistik Regresyon = SVC ("linear" kernel) > Naive Bayes Tabii sinir modelinde her defasında aynı sonuçlar elde edilmemektedir. Çünkü başlangıçtaki ağırlık değerlerinin ve sınama verilerinin belirlenmesinde rassallık vardır. Bu nedenle programı her çalıştırdığınızda sinir ağı modelinin performansı değişebilecektir. #---------------------------------------------------------------------------------------------------------------------------- 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) # SVC Solution 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 Input, Dense model = Sequential(name='BreastCancer') model.add(Input((training_dataset_x.shape[1],))) model.add(Dense(64, activation='relu', 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}') #---------------------------------------------------------------------------------------------------------------------------- Aşağıdaki örnekte "zambak (iris)" veri kümesine "rbf kernel" ile SVC, lojistik regresyon, Naive Bayes ve sinir ağı modelleri uygulanmıştır. Aşağıdaki gibi bir sonuç elde edilmiştir: SVC accuracy score: 0.9666666666666667 LogisticRegression accuracy score: 0.9666666666666667 GaussianNB accuracy score: 0.9333333333333333 NeuralNet accuracy score: 0.9666666666666667 Tabii programın her çalıştırılmasında yapay sinir ağı modeli ile elde edilen sonuçlar değişiklik gösterebilecektir. #---------------------------------------------------------------------------------------------------------------------------- import numpy as np 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.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='rbf') 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 sklearn.preprocessing import OneHotEncoder ohe = OneHotEncoder(sparse_output=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 Input, Dense model = Sequential(name='Iris') model.add(Input((training_dataset_x.shape[1],))) model.add(Dense(64, activation='relu', 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) 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_softmax = model.predict(scaled_test_dataset_x) predict_result_nn = np.argmax(predict_result_softmax, axis=1) 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 yanızca sınıflandırma problemlerinde değil regresyon problemlerinde de kullanılabilmektedir. Buna "destek vektör makineleri regresyonu (support vector machine regression)" denilmektedir. Destek vektör nakineleri regresyonu (kısaca "SVR" de denilmektedir) destek vektör makineleri ile doğrusal regresyonun birleşimi gibidir. Model noktaların dikme uzaklıklarının (residüellerin değil) minimize edilmesi esasına dayanmaktadır. SVR modelin hatalarını belirli bir marjin içinde tutmayı hedeflemektedir. Yani model yalnızca belirli bir tolerans seviyesindeki hatalarla kabul edilen veri noktalarıyla çalışır. Diğer veri noktaları için, modelin hataları belirli bir sınırdan fazla oluyorsa hata cezası uygulanmaktadır. scikit-learn içerisindeki sklearn.svm modülünde bulunan SVR sınıfı regresyon problemleri için 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 parametresinin yine default durumdu "rbf" biçimindedir. 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. SVR işleminden de yie bir heyperplane elde edilmektedir. Nesnenin coef_ özniteliği değişkenlerin katsayı vektörünü, intercept_ özniteliği ise eksen kesim noktasını vermektedir. Tabii yine bu öznitelikler yalnızca "linear kernel" kullanıldığında oluşmaktadır. Destek vektör makineleri regresyon problemlerine uygulanırken yine özellik ölçeklemesi yapılmalıdır. Aşağıda "Boston Housing Prices" veri kümesi üzerinde "rbf kernel" ile SVR kullanılarak bir regresyon oluşturulmuştur. Bu sonuçlar LinearRegression ve polinomsal 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.5789291858673096 Lasso Polynomial Mean Absolute Error: 3.5663928985595703 Aynı programda SVR için "linear" kernel kullanıldığında ise şu sonuçlar elde edilmiştir: SVR Mean Absolute Error: 3.312319479985529 LinearRegression Mean Absolute Error: 3.5789294242858887 Lasso Polynomial Mean Absolute Error: 3.5663931369781494 Bu veri kümesi için "linear" kernel kullanımının daha iyi sonuç verdiği 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.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.svm import SVR svr = SVR(kernel='linear') svr.fit(scaled_training_dataset_x, training_dataset_y) predict_result = svr.predict(scaled_test_dataset_x) from sklearn.metrics import mean_absolute_error mae = mean_absolute_error(predict_result, test_dataset_y) print(f'SVR Mean Absolute Error: {mae}') 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'LinearRegression Mean Absolute Error: {mae}') from sklearn.preprocessing import PolynomialFeatures pf = PolynomialFeatures(degree=2) transformed_training_dataset_x = pf.fit_transform(training_dataset_x) from sklearn.linear_model import Lasso lasso = Lasso(alpha=0.005, max_iter=100000) lasso.fit(scaled_training_dataset_x , training_dataset_y) predict_result = lasso.predict(scaled_test_dataset_x) from sklearn.metrics import mean_absolute_error mae = mean_absolute_error(predict_result, test_dataset_y) print(f'Lasso Polynomial Mean Absolute Error: {mae}') #---------------------------------------------------------------------------------------------------------------------------- #---------------------------------------------------------------------------------------------------------------------------- Makine öğrenmesindeki bazı yöntemler sonunda bir optimizasyon problemine evrilmektedir. Çözüm de bir fonksiyonun minimum değerinin elde edilmesiyle sağlanmaktadır. Örneğin yapay sinir ağlarında biz her batch işleminde loss fonksiyonu biçiminde isimlendirdiğimiz bir fonksiyonu minimize etmek için w derlerini ayarlıyorduk. Benzer biçimde istatistiksel lojistik regresyon, Lasso ve Ridge regresyonları bir minimizasyon problemine evrilmektedir. Biz de bu bölümde matematiksel optimizasyon üzerinde duracağız. Böylece pek çok makine öğrenmesi yönteminde sonuca nasıl varıldığı daha iyi anlaşılacaktır. Optimizasyon bağlamında minimizasyon ve maksimizasyon süreci aynı biçimde uygulanmaktadır. Ancak makine öğrenmesinde daha çok minimizasyon işlemleri uygulanmaktadır. #---------------------------------------------------------------------------------------------------------------------------- #---------------------------------------------------------------------------------------------------------------------------- Bir f(x) fonksiyonun değerini minimum yapan x değerleri nasıl elde edilmektedir? Bazı fonksiyonlarda minimizasyon işleminin bir kapalı çözümü (yani sembolik çözümü) söz konusu olsa da çoğu kez pratik bir kapalı çözüm oluşturulamamaktadır. Bu nedenle minimizasyon problemleri iteratif biçimde nümerik analiz yöntemleriyle gerçekleştirilmektedir. Her ne kadar lise ve üniversite matenatiğinde daha çok tek değişkenli fonksiyonlar üzerinde duruluyorsa da makine öğrenmesinde değişkenler özellikleri (yani sütunları) belirttiği için çok değişkenli fonksiyonlar söz konusu olmaktadır. Dolayısıyla y = f(x) gösteriminde x tek bir değerden (tek değişkenli fonksiyon) oluşabileceği gibi f((x1, x2, x2, ..., xn)) biçiminde birden fazla değerden de oluşabilmektedir. #---------------------------------------------------------------------------------------------------------------------------- #---------------------------------------------------------------------------------------------------------------------------- Fonksiyonları minimum yapan x değerlerinin elde edilmesi için çeşitli yöntem grupları kullanılabilmektedir. Bazı yöntem gruplarında fonksiyonun türevinden faydalanılır bunlara "tüeev kullanan yöntemler" diyebiliriz. Tabii bu yöntemleri uygulayabilmek için fonksiyonun türevlendirilebilir olması gerekir. İşte "türev kullanmayan" diğer bir yöntem grubu da vardır. Diğer bir yöntem grubuna da "stokastik (ya da probabilistic) yöntemler" de denilmektedir. Örneğin genetik algoritmalar bu sınıfa sokulabilir. Bazı yöntemlerde minimum bulunurken bazı kısıtlara da uyulmaya çalışılır. Yöneylem araştırmasındaki optimizasyon problemleri bu biçimdedir. Örneğin lagrange çarpımları, doğrusal programlama, kuadratik programlama gibi yöntemleri bu gruba dahil edebiliriz. Makine öğrenmesinde genellikle "türev kullanan yöntemler" tercih edilmektedir. Türev kullanan iki önemli yöntem "Newton-Raphson" ve "Gradient Descent" denilen yöntemlerdir. Newton-Raphson yöntemi daha hızlı bir yakınsama sağlıyor olsa da denetimli öğrenme için "Greadient Descent" yöntem çoğu kez daha uygun olmaktadır. #---------------------------------------------------------------------------------------------------------------------------- #---------------------------------------------------------------------------------------------------------------------------- 112. Ders - 23/03/2025 - Pazar #---------------------------------------------------------------------------------------------------------------------------- #---------------------------------------------------------------------------------------------------------------------------- Newton-Raphson özünde bir "nümerik kök bulma" yöntemidir. Bir fonksiyonun minimum değeri f'(x) değerini 0 yapan x değerleri olduğu için f'(x) = 0 fonksiyonun kökleri Newton-Raphson yöntemiyle bulunursa aslında fonksiyonu minimum yapan x değerleri de bulunmuş olur. Newton-Raphson kök bulma yönteminin çalışma mekanizması oldukça basittir. Belli bir x0 değerinden işlemlere başlanır. Bu x0 değeri için fonksiyonun türevi yani f'(x0) değeri fonksiyonun x0 noktasındaki teğetinin eğimine eşittir. Bir doğrunun eğiminin onun yatayla yaptığı açının tanjantına eşit olduğunu biliyoruz. O halde f'(x0) değeri aslında teğet doğrusunun tanyantına eşittir. Tanjantın dik üçgenlerde "karşı dik kenar / komşu dik kenar" oranı olduğunu anımsayınız. Burada karşı dik kenar f(x) komşu dik kenar ise x0 - x1 olduğuna göre aşağıdaki eşitlik elde edilecektir: eşitlik elde edilir: f'(x0) = f(x0) / (x0 - x1) Biz aslında x1 değerini elde etmek istiyoruz, x1 yalnız bırakılırsak aşağıdaki eşitliği elde ederiz: x1 = x0 - f(x0) / f'(x0) Böylece x0 verildiğinde artık köke daha yakın yeni bir x1 değeri elde edilmiş olur. Sonra x0 = x1 yapılıp aynı işlemler bir döngü içerisinde yinelenir. Pekiyi bu döngü ne zaman sonlandırılacaktır? İşte bunun için birbirinin yerini tutabilen çeşitli ölçütler kullanılabilmektedir. Örneğin: 1) |xo - x1| < epsilon oluştuğunda döngü sonlandırılabilir. 2) f(xo) < epsilon oluştuğunda döngü sonlandırılabilir. 3) f'(x0) < epsilon olduğunda döngü sonlandırılabilir. 4) Belli bir iterasyon sonra döngü sonlandırılabilir. Burada epsilon değeri ne kadar küçültülürse köke o kadar yaklaşılır. Ancak epsilon küçültüldükçe köke yaklaşma süresi de göreli biçimde uzayacaktır. Yaygın kullanılan epsilon değerleri 10^-6, 10^-8, 10^-10 gibi değerlerdir. Tabii bugün kullandığımız kayan noktalı float formatında yuvarlama hattaları da söz konusu olabilecektir. Yuvarlama hatalarından dolayı iterasyonlar devam ettirildiğinde tam 0 değeri de elde edilemeyebilir. Newton-Raphson yöntemi ile kök bulan bir fonksiyonu basit bir biçimde aşağıdaki gibi yazabiliriz: def newton_raphson(f, df, x0, epsilon=1e-10): while True: x1 = x0 - f(x0) / df(x0) if abs(x0 - x1) < epsilon: break x0 = x1 return x1 Tabii fonksiyonun birden fazla kökü varsa burada başlangıçtaki x0 değerinin seçimine göre köklerin hepsi değil köklerden herhangi biri bulunacaktır. Ancak yukarıda yazdığımız fonksiyonda önemli bir kusur vardır. Eğer fonksiyonun kökü yoksa sonsuz döngü oluşur. Bu nedenle belli bir iterasyona kadar kök bulunamazsa kökün olmadığı sonucu çıkartılarak döngü sonlandırılmalıdır. Fonksiyonu şöyle düzeltebiliriz: def newton_raphson(f, df, x0, epsilon=1e-10): for _ in range(MAX_ITER): x1 = x0 - f(x0) / df(x0) if abs(x0 - x1) < epsilon: return x1 x0 = x1 return None #---------------------------------------------------------------------------------------------------------------------------- MAX_ITER = 1000 def newton_raphson(f, df, x0, epsilon=1e-10): for _ in range(MAX_ITER): x1 = x0 - f(x0) / df(x0) if abs(x0 - x1) < epsilon: return x1 x0 = x1 return None def f(x): return x ** 2 - x - 6; def df(x): return 2 * x - 1; result = newton_raphson(f, df, 10) print(result) #---------------------------------------------------------------------------------------------------------------------------- Newton-Raphson yöntemi başlangıç noktasına göre tek bir kök bulmaktadır. Eğer birden fazla kök bulunmak isteniyorsa başlangıç noktalarının değiştirilmesi gerekir. Burada çeşitli yaklaşımlar söz konusu olabilir. Örneğin programcı belli x0 noktalarını oluşturup bunlar için kök bulmaya çalışabilir ya da bir kökü bulduktan sonra o noktadan biraz kayarak ilerleyebilir. Ancak her durumda Newton-Raphson aslında tek bir kök bulmaktadır. Biz birden fazla kök bulacak biçimde bir sarma fonksiyonu aşağıdaki gibi yazabiliriz: def find_all_roots(f, df, low, high, epsilon=1e-10): roots = [] xs = np.linspace(low, high, 10000) for x0 in xs: if root := newton_raphson(f, df, x0): flag = True for val in roots: if abs(val - root) < epsilon: flag = False break if flag: roots.append(root) return roots Burada low ve high arasında 10000 tane noktayı başlangıç noktası kabul ederek aynı denklemin kökleri bulunmaktadır. Tabii bu köklerin çoğu aynı olacaktır. Bu nedenle biz fonksiyon içerisinde kökleri roots isimli listede toplarken onun daha önce listede olup olmadığına baktık: for x0 in xs: if root := newton_raphson(f, df, x0): flag = True for val in roots: if abs(val - root) < epsilon: flag = False break if flag: roots.append(root) Noktalı sayılar üzerinde yuvarlama hataları oluşabileceğine dikkat ediniz. Bu nedenle biz burada bulunan köklerin gerçekten farklı olup olmadığı kontrolünü farkların mutlak değerini epsilon ile karşılaştrarak tespit etmeye çalıştık. #---------------------------------------------------------------------------------------------------------------------------- import numpy as np MAX_ITER = 1000 def newton_raphson(f, df, x0, epsilon=1e-10): for _ in range(MAX_ITER): x1 = x0 - f(x0) / df(x0) if abs(x0 - x1) < epsilon: return x1 x0 = x1 return None def find_all_roots(f, df, low, high, epsilon=1e-10): roots = [] xs = np.linspace(low, high, 10000) for x0 in xs: if root := newton_raphson(f, df, x0): flag = True for val in roots: if abs(val - root) < epsilon: flag = False break if flag: roots.append(root) return roots def f(x): return x ** 2 - x - 6; def df(x): return 2 * x - 1; roots = find_all_roots(f, df, -100, 100) print(roots) #---------------------------------------------------------------------------------------------------------------------------- Aşağıdaki örnekte x^8 - 5x^7 + 4x^2 - 8x + 2 biçiminde 8'inci dereceden bir polinomun kökleri bulunmuştur. Matematikte 5'inci dereceden sonra (5'inci derece de dahil olmak üzere) artık polinom kökleri için bir kapalı çözüm oluşturulamadığını anımsayınız. (Buna ilişkin teoreme Abel-Ruffini teoremi denilmektedir.) #---------------------------------------------------------------------------------------------------------------------------- import numpy as np MAX_ITER = 1000 def newton_raphson(f, df, x0, epsilon=1e-10): for _ in range(MAX_ITER) x1 = x0 - f(x0) / df(x0) if abs(x0 - x1) < epsilon: return x1 x0 = x1 return None def find_all_roots(f, df, low, high, epsilon=1e-10): roots = [] xs = np.linspace(low, high, 10000) for x0 in xs: if root := newton_raphson(f, df, x0): flag = True for val in roots: if abs(val - root) < epsilon: flag = False break if flag: roots.append(root) return roots def f(x): return x ** 8 - 5 * x ** 7 + 4 * x ** 2 - 8 * x + 2 def df(x): return 8 * x ** 7 - 35 * x ** 6 + 8 * x - 8 roots = find_all_roots(f, df, -1000, 1000) print(roots) #---------------------------------------------------------------------------------------------------------------------------- Newton-Raphson gibi türev kullanan yöntemlerde fonksiyonun türevini de oluşturmamız gerekir. Kapalı türev almak ise her zaman mümkün değildir. Mümkün olsa da çok zor uğraş olabilmektedir. Pekiyi bu durumda ne yapabiliriz? İşte bir fonksiyonun belli bir noktasındaki türevi için de nümerik yöntemler uygulanabilmektedir. #---------------------------------------------------------------------------------------------------------------------------- #---------------------------------------------------------------------------------------------------------------------------- Newton-Raphson yöntemiyle fonksiyonun minimum noktası f'(x) = 0 denklemiyle bulunabilir. Bu durumda iterasyonlar şöyle yapılacaktır: x1 = x0 - f'(x0) / f''(x0) Minimum noktaları bulan fonksiyon da şöyle oluşturulabilir: import numpy as np MAX_ITER = 1000 def newton_raphson(df, d2f, x0, epsilon=1e-10): for _ in range(MAX_ITER): x1 = x0 - df(x0) / d2f(x0) if abs(x0 - x1) < epsilon: return x1 x0 = x1 return None def newton_raphson_minimize(df, d2f, low, high, epsilon=1e-10): minimums = [] xs = np.linspace(low, high, 10000) for x0 in xs: if minimum := newton_raphson(df, d2f, x0): flag = True for val in minimums: if abs(val - minimum) < epsilon: flag = False break if flag: minimums.append(minimum) return minimums Nasıl bir fonksiyonun birden fazla kökü varsa birinci türevin sıfır olduğu birden fazla noktası da olabilir. Matematikte bu noktalara "yerel minimum (local minima)" denilmektedir. Yukarıdaki fonksiyon tüm yerel minimumları bulmaktadır. Bunların arasındaki en küçük değer ise global minimumdur. Bu nedenle yukarıdaki fonksiyonu ulacak global minimumu bşöyle düzeltebiliriz: def newton_raphson_minimize(df, d2f, low, high, epsilon=1e-10): global_minimum = None xs = np.linspace(low, high, 10000) for x0 in xs: if minimum := newton_raphson(df, d2f, x0): if global_minimum is None: global_minimum = minimum continue if abs(minimum - global_minimum) < epsilon: global_minimum = minimum return global_minimum Burada biz değişik x değerlerinden hareketle minimumlar bulmaya çalıştık. Bunlar arasında en küçük olanını elde ettik. Global minimum bulmak için daha gelişkin yöntemler de kullanılabilmektedir. Ancak biz kurusumuzda bunun üzerinde durmayacağız. #---------------------------------------------------------------------------------------------------------------------------- import numpy as np MAX_ITER = 1000 def newton_raphson(df, d2f, x0, epsilon=1e-10): for _ in range(MAX_ITER): x1 = x0 - df(x0) / d2f(x0) if abs(x0 - x1) < epsilon: return x1 x0 = x1 return None def newton_raphson_minimize(df, d2f, low, high, epsilon=1e-10): global_minimum = None xs = np.linspace(low, high, 10000) for x0 in xs: if minimum := newton_raphson(df, d2f, x0): if global_minimum is None: global_minimum = minimum continue if abs(minimum - global_minimum) < epsilon: global_minimum = minimum return global_minimum def f(x): return 3 * x ** 2 - 3 * x + 6 def df(x): return 6 * x - 3 def d2f(x): return 6 minimum = newton_raphson_minimize(df, d2f, -1000, 1000) print(minimum) #---------------------------------------------------------------------------------------------------------------------------- Pekiyi Newton-Raphson yönteminde birden fazla değişken varsa çözüm nasıl yapılmaktadır? Makine öğrenmesinde ve veri biliminde değişken sayısı sütun sayısı anlamına geldiği için çok değişkenli fonksiyonlar söz konusu olmaktadır. İşte çok değişkenli fonksiyonların minimum noktalarının tespit edilmesi için parçalı türevler kullanılmaktadır. Çok değişkenli bir fonksiyonun her değişken için türevi ayrı ayrı alınabilir. Bir değişken için türev alınırken diğer değişkenler sanki sabitmiş gibi işleme sokulmaktadır. Örneğin: f(x1, x2) = 3x1^2 + 2x1x2 - 6x2^2 + 3x1 - 2x2 + 8 Bu fonksiyonun x1 ve x2 için parçalı türevleri şöyle alınmaktadır: ∂f/∂x1 = 6x1 + 2x2 + 3 ∂f/∂x2 = -12x2 + 2x1 - 2 Buradan gördüğünüz gibi her değişken için bir parçalı türev elde edilmektedir. İşte matematikte parçalı türevlerden oluşan bu vektöre "radyan vektör (gradient vector)" denilmektedir. Gradyan vektör ters üçgenle (nabla) temsil edilir ve genel olarak bir sütun vektörü biçiminde ifade edilmektedir. Örneğin yukarıdaki fonksiyona ilişkin gradyen vektör şöyle gösterilebilir: ∇f = [ 6x1 + 2x2 + 3 -12x2 + 2x1 - 2 ] Bir fonksiyonun ikinci türevlerden oluşan matrise de matematike "Hessian Matrisi (Hessian Matrix)" denilmektedir. Hessian matrisi H sembolüyle temsil edilmektedir. Hesian matrisin genel biçimi şöyledir: H(f) = [ [∂²f/∂x₁², ∂²f/∂x₁∂x₂, ..., ∂²f/∂x₁∂xₙ], [∂²f/∂x₂∂x₁, ∂²f/∂x₂², ..., ∂²f/∂x₂∂xₙ], [⋮, ⋮, ⋱, ⋮], [∂²f/∂xₙ∂x₁, ∂²f/∂xₙ∂x₂, ..., ∂²f/∂xₙ²] ] Buradaki ∂²f/∂x₁∂x₂ gibi bir ikinci türev "önce x1'e göre parçalı türev al sonra da onun yeniden x2'ye göre parçalı türevini al" anlamına gelmektedir. Tabii ∂²f/∂x₁² türevi de aslında ∂²f/∂x₁∂x₁ türevi ile eşdeğerdir. Hessian matrisinin köşegenlerinin bu biçimde olduğuna dikkat ediniz. Biz daha önce Newton-Raphson yönteminde minimum noktayı aşağıdaki iterasyon ifadesiyle bulmuştuk: x_next = x_prev - f'(x_prev) / f''(x_prev) Eğer çok değişkenli bir fonksiyon söz konusuysa tabii xprev ve xnext değişken sayısı kadar bir vektör biçiminde olacaktır. İşte çok değişkenli fonksiyonlarda Newton-Raphson yöntemini uygularken birinci türev yerine gradyen vektör ikinci türev yerine de Hessian matrisi kullanılmaktadır: x_next = x_prev - transpose(H^-1(x_prev) @ ∇f(x_prev)) Tabii her iterasyonda x değerleri güncellendiğinde aslında elimizde değişken sayısı kadar x değeri olur. Pekiyi çok değişkenli fonksiyonlarda durdurma koşulu nasıl oluşturulabilir? Aslında durdurma için yukarıda belirttiğimiz yöntemlerin hepsi kullanılabilir. Örneğin |x_prev - x_next|< epsilon kuşuluna bakılabilir. Burada |x_prev - x_next| vektörün karşılıklı elemanlarının çıkartılmasıyla elde edilen vektörün uzaklığını (tipik olarak Öklit uzaklığını) temsil etmektedir. Biz bu iki vektörün çıkarımını x vektörü ile temsil edersek (yani x = |x_prev - x_next| dersek) bu uzaklık √(x₁² + x₂² + ... + xₙ²) biçiminde hesaplanmaktadır. Yukarıdaki iterasyon ifadesinde eğer ∇f bir sütun vektörü olduğuna göre H^-1(x_prev) @ ∇f(x_prev) matris çarpımından bir sütun vektörü elde edilir. x_prev bir satır vektörü olduüu için siz bu H^-1(x_prev) @ ∇f(x_prev) sütun vektörünün transpozesini almalısınız. Çok değişkenli fonksiyonların Newton-Raphson yöntemi ile minimum değerleri bulunurken yine rastgele bir noktadan başlanabilir. Ancak bu rastgele noktanın tek bir değer içermeyeceğine değişken sayısı kadar değere sahip olan bir satır vektörü olacağına dikkat ediniz. #---------------------------------------------------------------------------------------------------------------------------- #---------------------------------------------------------------------------------------------------------------------------- 113. Ders - 06/04/2025 - Pazar #---------------------------------------------------------------------------------------------------------------------------- #---------------------------------------------------------------------------------------------------------------------------- Bu noktada kısaca bir fonksiyonun belli bir noktasındaki türevinin nümerik analiz yöntemleriyle nasıl elde edileceği üzerinde de durmak istiyoruz. Nümerik türev işlemleri genellikle iteratif biçimde yapılmamaktadır. Türevin tanımını anımsayınız: lim (f(x + h) - f(x) ) / h h-> 0 Türev bir fonksiyonun belli bir noktadaki anlık değişimini belirtmektedir. Yukarıdaki ifadede h çok bir değer olarak alınırsa fonksiyonun x noktasındaki türevi elde edilebilir. Tabii burada h'nın aşırı küçük alınmaması gerekir. Çünkü bilgisayarlarımızda kullandığımız float türünün (C'nin, Java ve C#'ın double türünün) noktadan sonra belli bir duyarlılığı vardır. Bu duyarlılık yaklaşık 10^-16 civarındadır. Örneğin biz h değerini 10^-10 alabiliriz. Bu biçimde nümerik türev elde etmeye "ileri fark yöntemi (forward difference method)" denilmektedir. Bu yöntemde türev işlemi aşağıdaki gibi bir fonksiyonla yapılabilir: def derivative(f, x, h=1e-10): return (f(x + h) - f(x)) / h def f(x): return 3 * x ** 2 - 5 * x + 2 result = derivative(f, 2) print(result) # 7.000000579182597 Buradaki türevi alınacak fonksiyon 3x^2 - 5x + 2 fonksiyonudur. Biz bu fonksiyonun x = 2 noktasındaki türevini elde etmeye çalışık. Bu fonksiyonun x = 2 noktasındaki türevi 7'dir. Ancak biz burada tam 7 değerini elde edemedik. Çünkü yukarıda da belirttiğimiz gibi h değeri 0'a çok yakın değil 10^-10 olarak alınmıştır. Tabii bu tür durumlarda Python'un standart kütüphanesindeki Decimal sınıfını da kullanabiliriz. Bu sınıf işlemleri yapay bir biçimde fakat yüksek duyarlılıkta ve yuvarlama hatasına maruz kalmadan yapabilmektedir. Örneğin: import decimal def derivative(f, x, h=decimal.Decimal('0.'+'0' * 25 + '1')): return (f(x + h) - f(x)) / h def f(x): return 3 * x ** 2 - 5 * x + 2 decimal.getcontext().prec = 28 d = decimal.Decimal('2') result = derivative(f, d) print(result) # 7 Ancak Decimal sınıfı ile işlemlerin göreli biçimde yavaş olduğunu belirtmek istiyoruz. Yoğun hesap gerektiren durumlarda bu sınıfı kullanmayınız. Tabii fonkiyonun belli bir noktasında türevi tam ters bir biçimde x noktasından h kadar geriye gidilerek de elde edilebilir: lim (f(x) - f(x - h) ) / h h-> 0 Bu yönteme de "geri fark yöntemi (backward difference method)" denilmektedir. Genellikle bu iki yöntemin karma hali tercih edilmektedir. Buna da "merkezi fark yöntemi (central difference method)" denilmeketdir: lim (f(x + h) - f(x - h) ) / (2 * h) h-> 0 Bu yöntem Python'da şöyle uygulanabilir: def derivative(f, x, h=1e-10): return (f(x + h) - f(x - h)) / (2 * h) def f(x): return 3 * x ** 2 - 5 * x + 2 result = derivative(f, 2) print(result) # 7.000000579182597 Nümerik türev için başka yöntemler de kullanılabilmektedir. Ancak en yaygın kullanılan yöntem "merkezi fark" yöntemidir. SciPy kütüphanesinde merkezi fark yöntemi ile nümerik türev elde etmek için scipy.misc modülündeki derivative fonksiyonu bulundurulmuştur. Örneğin: from scipy.misc import derivative result = derivative(f, 2) print(result) Ancak SciPy'ın son versiyonlarında misc modülü "deprecated" yapılmış ve bu fonksiyon scipy.differentiate isimli bir modül oluşturularak oraya taşınmıştır. Kullandığınız SciPy versiyonuna ve bu fonksiyonun nerede bulunduğuna dikkat ediniz. #---------------------------------------------------------------------------------------------------------------------------- def derivative(f, x, h=1e-10): return (f(x + h) - f(x - h)) / (2 * h) def f(x): return 3 * x ** 2 - 5 * x + 2 result = derivative(f, 2) print(result) # 7.000000579182597 import decimal def derivative(f, x, h=decimal.Decimal('0.'+'0' * 25 + '1')): return (f(x + h) - f(x)) / h def f(x): return 3 * x ** 2 - 5 * x + 2 decimal.getcontext().prec = 28 d = decimal.Decimal('2') result = derivative(f, d) print(result) from scipy.misc import derivative result = derivative(f, 2) print(result) #---------------------------------------------------------------------------------------------------------------------------- Pekiyi nümerik biçimde parçalı türev nasıl elde edilmektedir? Aslında işlemler benzer bir biçimde yürütülmektedir. Örneğin fonkisyonumuz şöyle olsun: f(x1, x2, x3) = 3x1^2 + 2x1^2 x3^2 2x1 x2 x3 + 2x2 - x3 + 8 Burada bu fonksiyonun x1 = a, x2 = b, x3 = c için x3'e göre parçalı türevini elde etmek isteyelim. Yapılacak şey aynıdır: ∂f/∂x3 = lim (f(x1, x2, x3 + h) - f(x1, x2, x3)) / h h -> 0 Görüldüğü gibi burada biz yalnızca parçalı türevini elde edeceğimiz değişkene küçük bir artım verdik. #---------------------------------------------------------------------------------------------------------------------------- #---------------------------------------------------------------------------------------------------------------------------- Biz yukarıda Newton-Raphson yöntemini açıkladık. Aslında makine öğrenmesinde Newton-Raphson yerine daha çok "gradient descent" denilen yöntem tercih edilmektedir. Newton Rapson yöntemindeki ikinci türevler yakınsamayı hızlandırmakla birlikte denetimli yöntemlerde karmaşıklığa yol açmaktadır. Gradient descent yöntem bir kök bulma yöntemi değildir, minimum bulma yöntemidir. Gradient descent yöntemde ikinci türevler kullanılmadığı için yakınsama Newton-Raphson yöntemine göre daha geç gerçekleşmektedir. Ancak ikinci türevlerin ortadan kaldırılması yöntemin uygulanabilirliğini oldukça artırmaktadır. #---------------------------------------------------------------------------------------------------------------------------- #---------------------------------------------------------------------------------------------------------------------------- Gradient descent de türev kullanan bir yöntemdir. Ancak bu yöntemde fonksiyonun ikinci türevi değil birinci türevi kullanılır. Fonksiyonun birinci türevi bize bir doğrultu verir. Biz de o doğrultuda yavaş yavaş ilerleyerek hedefe ulaşırız. Gradient descent yöntem Newton-Raphson yöntemine göre hedefe daha geç ulaşmaktadır ancak makine öğrenmesi uygulamaları için daha elverişli bir yöntemdir. Bu yöntemle yakınsama aşağıdaki iterasyon ifadesi ile yapılır: x_new = x_old - α * ∇f(x_old) Burada f fonksiyonu çok değişkenli bir fonksiyon olduğu için f'in türevi parçalı bir biçimde gradient vektör olarak elde edilmelidir. Tabii eğer f fonksiyonu tek değişkenli ise bu iterasyonu şöyle de temsil edebiliriz: x_new = x_old - α * f'(x_old) Burada alpha değerine "öğrenme hızı (learning rate)" denilmektedir. Bu öğrenme hızı uygulamacı tarafından belirlenir. Yukarıda da belirttiğimiz gibi fonksiyonun birinci türevi bize bir doğrultu verir. Biz de o doğrultuda adım adım ilerleriz. Ancak adım büyüklüğünü istediğimşz gibi ayarlayabiliriz. Buradaki öğrenme hızı bu adım büyüklüğünü ayarlamkta kullanılmaktadır. Gradient descent yöntemi şuna benzetebiliriz. Biz bir dağın tepesinde olalım ve hızlı bir biçimde aşağıya yani ovaya inmek isteyelim. Bunu nasıl yapabiliriz? Tabii ki en yüksek irtifa kaybettiren yolu, yani daha dik yolu seçeriz. İşte bu benzetmede birinci türev aslında bize en yüksek irtifayı kaybettirecek doğrultuyu vermektedir. Tabi biz o doğrultuda büyük adımlarla ya da küçük adımlarla ilerleyebiliriz. Daha önceden de belirttiğimiz gibi büyük adımlarla ilerlerken hedefi biraz kaçırabiliriz. Ancak büyük adımlar hedefe daha kolay yaklaşmamızı sağlar. İşte yukarıdaki iterasyon ifadesindeki alpha değeri bu adımı ayarlamakta kullanılmaktadır. Burada alpha parametresi kullanıldıktan sonra türev ifadesinin değerinin önemsizleştiğini sanabilirsiniz. Gerçekten de biz türevden yalnızca bir doğrultu elde edip bir büyüklük elde etmesek de algoritma yine çalışacaktır. Ancak yukarıdaki dağ örneğinde belirttiğimiz gibi aslında bu türev ifadesi bizim adımlarımızı da ayarlamaktadır. Eğer türev değeri yüksekse biz daha büyük adımlarla, düşükse daha düşük adımlarla ilerleriz. Buradaki alfa değeri aslında bir hyper parametredir. Yani duruma bizim müdahale etmeemizi sağlayan bir üst parametredir. Gradient descent yöntemde alpha ile temsil ettiğimiz "öğrenme hızı (learning rate)" sabit bir değer olarak (örneğin 10^-5 gibi) alınabilir. Ancak bu değer ne kadar küçültülürse hedefe varmak için gereken iterasyon sayısı artacaktır. Bu nedenle öğrenme hızını sabit almayıp onu dinamik bir biçimde de değiştirebiliriz. Örneğin önce büyük adımlarla hedefin yanına yaklaşıp orada adımlarımızı küçültebiliriz. Genellikle bu tür yöntemler tercih edilmektedir. Pekiyi bu yöntemde iterasyonlar ne zaman sonlandırılmaktadır? Aslıda sonlandırmalar daha önce görmüş olduğumuz Newton-Raphson yöntemindeki gibi yapılabilir. Bazı sonlandırma stratejileri şöyle olabilir: 1) |x_new - x_old| < epsilon oluştuğunda iterasyonlar sonlandırılabilir. 1) |f(x_new) - f(x_old)| < epsilon oluştuğunda iterasyonlar sonlandırılabilir. 3) |∇f(x_new)| < epsilon olduğunda iterasyonlar sonlandırılabilir. 4) Belli bir iterasyon aypıldıktan sonra iterasyonlar sonlandırılabilir. Gradient descent yöntemle tek değişkenli bir fonksiyonun minimum noktasını bulan bir fonksiyon aşağıdaki gibi yazılabilir: def gradient_descent(f, f_d, x, *, niter=None, alpha=1e-5, maxiter=10_000_000): if niter != None and niter > maxiter: raise ValueError(f'niter must be less than max_iter. Your max_iter is = {maxiter}') x_old = x count = 0 for _ in range(maxiter): x_new = x_old - alpha * f_d(x_old) if niter != None: if count >= niter: return x_new, f(x_new) count += 1 else: if abs(f(x_new) - f(x_old)) < 1e-15: return x_new, f(x_new) x_old = x_new return None Burada f parametresi minimize edilecek fonksiyonu f_d parametresi ise bu fonksiyonun türev fonksiyonunu belirtmektedir. Bitiş koşulu iki biçimde ayarlanabilmektedir. Eğer niter parametresi için bir argüman girilmemişse bitiş koşulu için iki değer arasındaki fark kullanılmaktadır. Eğer niter parametresi için argüman girilmişse bitiş koşulu iterasyon miktarıyla belirlenmektedir. Fonksiyonun ikili bir demete geri döndüğüne dikkat ediniz. Fonksiyonun maxiter parametresi minimuma sahip olmayan fonksiyonlarda iterasyonları durdurmak için bulundurulmuştur. Örnek bir kullanım şöyle olabilir: def f(x): return 3 * x ** 2 - 6 * x + 2 def f_d(x): return 6 * x - 6; t = gradient_descent(f, f_d, 10, niter=1000000) if t != None: x, minval = t print(x, minval) else: print('no minimum') #---------------------------------------------------------------------------------------------------------------------------- def f(x): return 3 * x ** 2 - 6 * x + 2 def f_d(x): return 6 * x - 6 def gradient_descent(f, f_d, x, *, niter=None, alpha=1e-5, maxiter=10_000_000): if niter != None and niter > maxiter: raise ValueError(f'niter must be less than max_iter. Your max_iter is = {maxiter}') x_old = x count = 0 for _ in range(maxiter): x_new = x_old - alpha * f_d(x_old) if niter != None: if count >= niter: return x_new, f(x_new) count += 1 else: if abs(f(x_new) - f(x_old)) < 1e-15: return x_new, f(x_new) x_old = x_new return None t = gradient_descent(f, f_d, 10, niter=1000000) if t != None: x, minval = t print(x, minval) else: print('no minimum') #---------------------------------------------------------------------------------------------------------------------------- Gradient descent yöntemde de başlangıç noktasına bağlı olarak biz yerel minimumlara yakalanabiliriz. Global minimum değerin elde edilmesi için yine Newton-Raphson yönteminde sözünü ettiğimiz yöntemler kullanılabilmektedir. Anımsayacağınız gibi en basit yöntem buradaki başlangıç x değerini değiştirip global minimumu yakalamaya çallışmaktır. Aşağıda örnekte yöntem uygulanmıştır. Ancak aslında global minimumu daha az denemede belirlemek için daha gelişmiş yöntemler de kullanılabilmektedir. #---------------------------------------------------------------------------------------------------------------------------- import numpy as np def f(x): return 3 * x ** 2 - 6 * x + 2 def f_d(x): return 6 * x - 6 def gradient_descent(f, f_d, x, *, niter=None, alpha=1e-5, maxiter=10_000_000): if niter != None and niter > maxiter: raise ValueError(f'niter must be less than max_iter. Your max_iter is = {maxiter}') x_old = x count = 0 for _ in range(maxiter): x_new = x_old - alpha * f_d(x_old) if niter != None: if count >= niter: return x_new, f(x_new) count += 1 else: if abs(f(x_new) - f(x_old)) < 1e-15: return x_new, f(x_new) x_old = x_new return None def gradient_descent_minimize(df, d2f, low, high, epsilon=1e-1, **kwargs): global_minimum = None xs = np.linspace(low, high, 100) for x0 in xs: t = gradient_descent(df, d2f, x0, **kwargs) if t: x, minimum = t if global_minimum is None: global_minimum = minimum continue if abs(minimum - global_minimum) < epsilon: global_minimum = minimum return x, global_minimum x, minval = gradient_descent_minimize(f, f_d, -10000, 1000, niter=1000000) print(x, minval) #---------------------------------------------------------------------------------------------------------------------------- Tabii biz aslında yukarıdaki fonksiyonlara yalnızca asıl fonksiyonu parametre olarak geçirebiliriz, türev işlemini de nümerik yöntemle gerçekleştirebiliriz. Aşağıda böyle bir örnek veriyoruz. #---------------------------------------------------------------------------------------------------------------------------- import numpy as np def derivative(f, x, h=1e-10): return (f(x + h) - f(x - h)) / (2 * h) def f(x): return 3 * x ** 2 - 6 * x + 2 def gradient_descent(f, x, *, niter=None, alpha=1e-5, maxiter=10_000_000): if niter != None and niter > maxiter: raise ValueError(f'niter must be less than max_iter. Your max_iter is = {maxiter}') x_old = x count = 0 for _ in range(maxiter): x_new = x_old - alpha * derivative(f, x_old) if niter != None: if count >= niter: return x_new, f(x_new) count += 1 else: if abs(f(x_new) - f(x_old)) < 1e-15: return x_new, f(x_new) x_old = x_new return None def gradient_descent_minimize(df, low, high, epsilon=1e-1, **kwargs): global_minimum = None xs = np.linspace(low, high, 10) for x0 in xs: t = gradient_descent(df, x0, **kwargs) if t: x, minimum = t if global_minimum is None: global_minimum = minimum continue if abs(minimum - global_minimum) < epsilon: global_minimum = minimum return x, global_minimum x, minval = gradient_descent_minimize(f, -10, 100, niter=1000000) print(x, minval) #---------------------------------------------------------------------------------------------------------------------------- Daha önce de belirttiğimiz gibi çok değişkenli fonksiyonlarda gradient descent yönteminde parçalı türevler kullanılmaktadır. Parçalı türevlerden oluşan vektöre de gradyan vektör dendiğini anımsayınız. Örneğin bu gradient descent yöntemiyle iki değişkenli bir fonksiyonun minimum noktasını bulmaya çalışalım. Fonksiyonumuz şöyle olsun: f(x1, x2) = 3x1^2 + 2x2^2 - 3x1x2 + 2x1 + x2 + 7 Bu fonksiyonun x1 ve x2 için parçalı türevlerini elde edelim: ∂f/∂x1 = 6x1 - 3x2 + 2 ∂f/∂x2 = 4x2 - 3x1 + 1 Böylece gradyan vektör şöyle oluşturulur: ∇f = [ 6x1 - 3x2 + 2 4x2 - 3x1 + 1 ] Gradient descent iterasyon ifadesi şöyleydi: x_new = x_old - α * ∇f(x_old) Burada artık x_old ve x_new bir sütun vektörü olacaktır. Tabii biz bunları tek boyutlu dizi olarak oluşturup karşılıklı elemanla üzerinde de işlem yapabiliriz. Tabii artık burada başlangıç noktası tek bir x değeri değil x1 ve x2 değerlerinden oluşan bir vektör olacaktır. İzleyen paragraflarda buna ilişkin örnekler vereceğiz. #---------------------------------------------------------------------------------------------------------------------------- #---------------------------------------------------------------------------------------------------------------------------- 114. Ders - 12/04/2025 - Cumartesi #---------------------------------------------------------------------------------------------------------------------------- #---------------------------------------------------------------------------------------------------------------------------- Amaç fonksiyonlarının (loss fonksiyonlarının) parçalı türevlerinin alınması konusunun iyi anlaşılması için bileşke fonksiyonların türevlerinin nasıl alındığının bilinmesi gerekmektedir. Bir fonksiyonun çıktısının başka bir fonksiyona girdi yapılması durumuna matematikte "bileşke fonsiyon (composite function)" denilmektedir. Örneğin biz g(x) fonksiyonundan elde edilen değeri f(x) fonksiyonuna sakup bir değer elde etmek isteyelim. Bu durum programlamada f(g(x)) biçiminde bir çağırma ile yapılmaktadır. Matematikte ise bu işlem (fog)(x) ile gösterilmektedir. (fog)(x) tamamen f(g(x)) ile aynı anlamdadır. Tabii bileşke fonksiyonlar da birbirini izleyebilir. Örneğin biz h(x) fonksiyonun çıktısını g(x) fonksiyonuna girdi yapabiliriz, g(x) fonksiyonunun çıktısını da f(x) fonksiyonuna girdi yapabiliriz. Bu durum matematikte (fogoh)(x) biçiminde gösterilmektedir. Bu ifade de f(g(h(x))) ile eşdeğerdir. #---------------------------------------------------------------------------------------------------------------------------- #---------------------------------------------------------------------------------------------------------------------------- Bir fonksiyon matematikte iki biçimde gösterilmektedir: 1) Fonksiyonel biçimde (functional form) yani f(x) = ... biçiminde. 2) Denklemsel biçimde (equation form) yani y = ... biçiminde. Eğer fonksiyon fonksiyonel biçimde belirtiyorsa bunun türevi de f'(x) = ... biçiminde gösterilmektedir. Bu gösterime "Lagrange Notasyonu" da denilmektedir Eğer fonksiyon denklemsel biçimde belirtilmişse bunun türevi de dy/dx = ... biçiminde gösterilmektedir. Buna da "Leibniz Notayonu" denilmektedir. Ayrıca d/dx (x^2 - 1) biçimindeki ifadeler de "x^2 - 1'in türevini al" anlamına gelmektedir. Çok değişkenli fonksiyonlarda parçalı türevler söz konusu olduğu için türev ifadesi de genel olarak ∂f/∂x ile gösterilmektedir. Burada ∂x parçalı türevi alınacak değişkeni belirtmektedir. #---------------------------------------------------------------------------------------------------------------------------- #---------------------------------------------------------------------------------------------------------------------------- Bileşke fonksiyonun türevi şöyle oluşturulmaktadır: (fog)'(x) = f'(g(x)) * g'(x) Bileşke fonksiyonun bu türev ifadesine "türevde zincir kuralı (chain rule)" da denilmektedir. Bu ifade şöyle de yazılabilir: (fog)'(x) = df/dg * dg/dx Örneğin biz y = (x - 2)^2 fonksiyonunun türevini almak isteylim. Bu türevi almanın bir yolu karesel ifadeyi açıp elde edilen ifadenin türevini almaktadır. Karesel ifadeyi açalım: y = x^2 - 4x + 4 Şimdi türev alalım: dy/dx = 2x - 4 Şimdi y = (x - 2)^2 fonksiyonunu bileşke fonksiyon olarak ele alıp zincir kuralıyla türev alalım. x - 2 fonksiyonuna denkelmsel biçimde u diyelim: u = x - 2 y = u^2 Şimdi zincir kuaralını uygulayalı.ç dy/dx = dy/du * du/dx = 2u * 1 Şimdi u yerine x - 2 ifadesini yerleştirelim: dy/dx = 2 * (x - 2) = 2x - 4 #---------------------------------------------------------------------------------------------------------------------------- #---------------------------------------------------------------------------------------------------------------------------- Şimdi de doğrusal regresyonu gradient descent yöntemle çözmeye çalışalım. Tabii aslında doğrusal regresyonun gradient descent yöntemle çözülmesinin bir anlamı yoktur. Zaten doğrusal regresyonun kapalı çözümü vardır. Bu kapalı çözüme "en küçük kareler (ordinary least squares)" yöntemi dendiğini anımsayınız. Ancak biz burada gradient descent yöntemin doğrusal regresyon üzerinde bir uygulamasını yapmak istiyoruz. Bunun için önce en küçüklemeye çalıştığımız loss fonksiyonun (amaç fonksiyonu) her değişken için türevlerini alarak gradyan vektörü elde ederiz. Sonra da adım adım hedefe varmaya çalışırız. Doğrusal regresyonda amaç fonksiyonu "doğru denkleminden elde edilen değerle (yi_hat) gerçek değerlerin (yi) farklarının karelerinin toplamının ortalaması biçiminde" ifade edilebilir. L = Σ(yi_hat - yi)² / N Şimdi burada biz L'nin b0 ve b1 için parçalı türevlerini elde edeceğiz. Buradaki y_hat teriminin aslında b0 + b1x1 + b2x2 + ... + bnxn anlamına geldiğine dikkat ediniz. Önce bu karesel açımı yapalım: L = Σ(yi_hat^2 - 2yi_hatyi + yi^2) / N Burada türevde zincir kuralını uygulayarak b0'a göre parçalı türevi elde edelim: ∂L/∂b0 = (Σ(2yi_hat - 2yi) / N) * 1 ∂L/∂b0 = (2 / N) * Σ(yi_hat - yi) Şimdi yine zincir kuralı ile bj'lere göre parçalı türevi elde edelim: ∂L/∂bj = Σ[(2yi_hat - 2yi) / N * xi] ∂L/∂bj = (2 / N) * Σ[(yi_hat - yi) * xi] Şimdi her iki parçalı türev ifadesini yeniden alt alta yazalım: ∂L/∂b0 = (2 / N) Σ(yi_hat - yi) ∂L/∂bj = (2 / N) Σ[(yi_hat - yi) * xj] Minimizasyon problemlerinde aslında sabit çarpanların temelde önemi yoktur. Yani örneğin x^2 değerini minimize etmekle 3x^2 değerini minimize etmek arasında ya da (1/3)X^2 minimize etmek arasında bizim için bir fark yoktur. (Burada yanlış anlamayın bu fonksiyonların minimum değerleri farklıdır ancak minimize etmek için yapılacak türev işlemleri arasında bir farklılık yoktur.) Bu nedenle kolaylık sağlamak amacıyla bu tür çarpanları tamamen atabiliriz: ∂L/∂b0 = Σ(yi_hat - yi) ∂L/∂bj = Σ[(yi_hat - yi) * xj] Bu tür minimizasyon işlemlerinde sabit çarpanları muhafaza etmenin bazen minimizasyona bir stabilite kazandırmakta faydası olabilmektedir. Ancak bu stabilite gradyan işleminde alpha katsayısı ile de ayarlanabilmektedir. Biz yukarıda belli bir bj değerini bulduk. Tüm bj değerlerini matrisel biçimde de ifade edebliriz: ∂L/∂b = X.T @ (y_hat - y) Buradan da gradient için iterasyon ifadesi şöyle oluşturulabilir: b0_new = b0_old - alpha * Σ(yi_hat - yi) b_new = b_old = alpha * X.T @ (y_hat - y) Burada y_hat x değerlerinin b0 + b1x1 + b2x2 + b3x3 + ... + bnxn doğru denkleminde yerine konularak elde edilen değeri belirtmektedir. Tabii aslında yukarıdaki iki türev ifadesini tek bir ifade haline de getirebiliriz. İki türev ifadesine bir daha dikkat ediniz: ∂L/∂b0 = Σ(yi_hat - yi) ∂L/∂b = X.T @ (y_hat - y) Eğer biz X'e 1'lerle dolu bir sütun eklersek bu sütun zaten b0'ı temsil eder hale gelir: ∂L/∂b = X.T @ (y_hat - y) Bu durumda iterasyon ifadesi de şöyle olur: b_new = b_old = alpha * X.T @ (y_hat - y) Şimdi de yukarıdaki genel formülü basit doğrusal regresyona uygun hale getirelim. Basit doğrusal regresyon için elde edilecek doğru denkleminin b0 + b1x olduğunu anımsayınız. ∂L/∂b0 = Σ(yi_hat - yi) ∂L/∂b1 = Σ[(yi_hat - yi) * xi] Aşağıda basit doğrusal regresyon için bu işlemlerin nasıl yapıldığı gösterilmiştir. #---------------------------------------------------------------------------------------------------------------------------- import numpy as np import pandas as pd df = pd.read_csv('points.csv') dataset_x = df['x'] dataset_y = df['y'] def loss(y_hat, y): return np.sum((y_hat - y) **2) def linear_regression_gradient(x, y, *, niter = None, epsilon=1e-10, learning_rate): b0 = 0 b1 = 0 prevloss = 0 count = 0 while True: y_hat = b0 + b1 * x df_b0 = np.sum(y_hat - y) df_b1 = np.sum((y_hat - y) * x) b0 = b0 - df_b0 * learning_rate b1 = b1 - df_b1 * learning_rate if niter != None: if count >= niter: break count += 1 nextloss = loss(b0 + b1 * x, y) if np.abs(prevloss - nextloss) < epsilon: break prevloss = nextloss return b0, b1 b0, b1 = linear_regression_gradient(dataset_x, dataset_y, niter=100, 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') plt.xlabel('x') plt.ylabel('y') plt.scatter(dataset_x, dataset_y, color='blue') plt.plot(x, y, color='red') plt.show() print(f'Slope = {b1}, Intercept={b0}') #---------------------------------------------------------------------------------------------------------------------------- Şimdi de lojistik regresyon problemini gradient descent yöntemle çözelim. Anımsanacağı gibi lojistik regresyon için maximum liklihood amaç fonksiyonu kullanılıyordu. Ancak maximum likelihood bir maksimizasyon problemi olduğu için biz bunun negatiflisini miniznimize etmeye çalışıyorduk: L = -∑[(1 - yi) * log(1 - p(xi)) + yi * log(p(xi))] Burada p(x) fonksiyonun sigmoid fonksiyonunu belirttiğini anımsayınız. Biz buradaki p(x) fonksiyonu yerine y_hat de yazabiliriz: L = -∑[(1 - yi) * log(1 - yi_hat)) + yi * log(yi_hat))] Biz buradan yine b0 + b1x1 + b2x2 + b3x3 + .... + bnxn biçiminde bir doğru denklemi elde etmeye çalışacağız. Bu fonksiyonun b0 ve bj'ler için parçalı türevleri bileşik fonksiyonların türevleri ile zincir kuralı uygulanarak elde edilebilmektedir. b0 için elde edilen parçalı türev şöyledir: ∂L/∂b0 = Σ(yi_hat - yi) Diğer bj katsayıları için türevi de şöyledir: ∂L/∂bj = Σ[(yi_hat - yi) * xj] Burada veri kümesinde n tane satır k tane sütun olduğunu varsayalım. Buradaki toplam i'ler üzerinde yürütülmektedir. Yani buradaki toplam i = 1'den n'e kadar gitmektedir. Bu ifadeyi matrisel biçimde yazabiliriz: ∂L/∂b = X.T @ (y_hat - y) ∂L/∂b0 ve ∂L/∂b ifadelerine dikkat ediniz: ∂L/∂b0 = Σ(yi_hat - yi) ∂L/∂b = X.T @ (y_hat - y) Aslında yine X matrisine 1'lerden oluşan bir sütun ekleyerek bu iki ifadeyi tek bir matris ifadesi olarka da birleştirebiliriz: ∂L/∂b = X.T @ (y_hat - y) İfadeyi bu biçimde tek bir matrsi ifadesine dönüştürdüğümüzü varsayarsak gradient descent iterasyon ifadesi şöyle olacaktır: h = sigmoid(dataset_x @ b) error = h - dataset_y grad = dataset_x.T @ error b = b - learning_rate * grad Aşağıda bu yöntemle lojistik regresyon örneği verilmiştir. Gradeint descent yönteminin uygulandığı fonskiyon şöyledir: def gradient_descent_logistic(dataset_x, dataset_y, learning_rate=0.001, niter=50000): b = np.zeros((dataset_x.shape[1], 1)) for k in range(niter): h = sigmoid(dataset_x @ b) error = h - dataset_y grad = dataset_x.T @ error b = b - learning_rate * grad return b Aşağıdaki iki özellikli bir veri kümesi üzerinde gradient descent yöntemiyle lojistik regresyon uygulanmıştır. Buarada 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 -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 #---------------------------------------------------------------------------------------------------------------------------- import numpy as np from sklearn.preprocessing import StandardScaler 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() ss = StandardScaler() scaled_dataset_x = ss.fit_transform(dataset_x) scaled_dataset_x = np.append(scaled_dataset_x, np.ones((dataset_x.shape[0], 1)), axis=1) import matplotlib.pyplot as plt plt.figure(figsize=(8, 6)) 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, niter=50000): b = np.zeros((dataset_x.shape[1], 1)) for k in range(niter): h = sigmoid(dataset_x @ b) error = h - dataset_y grad = dataset_x.T @ error b = b - learning_rate * grad return b b = gradient_descent_logistic(scaled_dataset_x, dataset_y.reshape(-1, 1)) x1 = np.linspace(-5, 5, 1000) x2 = (-b[2] - b[0] * x1) / b[1] points = np.vstack((x1, x2)).T transformed_points = ss.inverse_transform(points) 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(transformed_points[:, 0], transformed_points[:, 1], color='red') plt.xlabel('x1') plt.ylabel('x2') plt.legend(['class 0', 'class 1', 'regression line']) plt.show() #---------------------------------------------------------------------------------------------------------------------------- 115. Ders - 13/04/2025 - Pazar #---------------------------------------------------------------------------------------------------------------------------- #---------------------------------------------------------------------------------------------------------------------------- Biz yukarıdaki örneklerde tüm veri kümesini tek hamlede işleme soktuk. Aslında gradient descent yöntemin önemli bir avantajı veri kümesini parçalara ayırarak parça parça eğitime daha iyi olanak sağlamasıdır. Böylece bir döngü içerisinde eğitim parça parça yapılabilmektedir. Gradient descent yöntemde eğitimin parça parça yapılmasına yönelik birkaç teknik kullanılabilmektedir. Bu tekniklere "batch gradient descent", "stochastic gradient descent" ve "mini batch gradient descent" denilmektedir. "Batch gradient descent" her iterasyonda tüm veri kümesinin bir bütün olarak işleme sokulması anlamına gelmektedir. Örneğin elimizde 1000 satırlık bir veri kümesi olsun. Biz her iterasyonda bu 1000 satırın hepsini işleme sokarsak "batch gradient descent" tekniğini kullanmış oluruz. Yukarıda ilk yaptığımız örnek bu biçimdeydi. "Stochastic gradient descent" her defasında rastgele bir satır seçilerek iterasyonunun satır satır yapılması anlamına gelmektedir. Örneğin 1000 satırlık veri kümesinde bir döngü içerisinde biz her iterasyonda rastgele bir satırı seçip iterayonu yalnızca bu satırla yaparsak "stochastic gradient descent" tekniğini kullanmış oluruz. "Mini-batch gradient descent" ise her defasında tüm satırları değil, tek bir satırı da değil, bir grup satırı kullanarak iterasyon yapmak anlamına gelmektedir. Örneğin biz 1000 satırlık veri kümesinde her iterasyonda 100 satırı tek hamlede işleme sokarsak "mini-batch gradient descent" tekniğini kullanmış oluruz. Tabii aslında "mini-batch gradient descent" yönteminde de iterasyona sokulacak bir grup satır rastgele seçilebilir. Yani aslında bu yöntem de "stochastic" biçimde kullanılabilmektedir. Bizim yapay sinir ağlarında aslında "mini-batch gradient descent" kullanmış olduğumuza dikkat ediniz. Gerçekten de en çok tercih edilen yöntem "mini-batch gradient descent" yöntemidir. Bu yöntemde genellikle uygulamacı önce veri kümesini karıştırır sonra onu batch batch işleme sokar. Aşağıdak "mini-batch-gradient descent" lojistik regresyona bir örnek verilmiştir. #---------------------------------------------------------------------------------------------------------------------------- import numpy as np from sklearn.preprocessing import StandardScaler 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() ss = StandardScaler() scaled_dataset_x = ss.fit_transform(dataset_x) scaled_dataset_x = np.append(scaled_dataset_x, np.ones((dataset_x.shape[0], 1)), axis=1) import matplotlib.pyplot as plt plt.figure(figsize=(8, 6)) 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, batch_size, learning_rate=0.001, niter=50000): b = np.zeros((dataset_x.shape[1], 1)) nbatches = dataset_x.shape[0] // batch_size for batch_no in range (nbatches): x = dataset_x[batch_no * batch_size:batch_no * batch_size + batch_size] y = dataset_y[batch_no * batch_size:batch_no * batch_size + batch_size] for k in range(niter): h = sigmoid(x @ b) error = h - y grad = x.T @ error b = b - learning_rate * grad return b b = gradient_descent_logistic(scaled_dataset_x, dataset_y.reshape(-1, 1), batch_size=10) x1 = np.linspace(-5, 5, 1000) x2 = (-b[2] - b[0] * x1) / b[1] points = np.vstack((x1, x2)).T transformed_points = ss.inverse_transform(points) 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(transformed_points[:, 0], transformed_points[:, 1], color='red') plt.xlabel('x1') plt.ylabel('x2') plt.legend(['class 0', 'class 1', 'regression line']) plt.show() #---------------------------------------------------------------------------------------------------------------------------- Şimdi de sinir ağlarında w ve bias değerlerinin gradient descent yöntemde nasıl güncellendiği üzerinde duralım. Yukarıda da belirttiğimiz gibi sinir ağlarında genellikle "mini batch gradient descent" yöntem kullanılmaktadır. Yani bir batch veri ağa girdi olarak verilir. Bu batch veriden bir çıktı elde edilir. Sonra bu çıktıdan hareketle gradyanlar hesaplanır ve bu gradyanlar tüm nörünların w ve bias değerlerini güncellemekte kullanılır. Sinir ağlarında bir batch bilginin ağa verilip bundan çıktı elde edilmesine "ileri yayılım (forward propagation)", geriye doğru gidilerek nöronların w ve bias değerlerinin güncellenmesine de "geriye doğru yayılım (back propagation)" denilmektedir. İleriye doğru yayılımın nasıl yapıldığını zaten biliyoruz. Burada geri doğru yayılımın nasıl yapıldığı üzerinde duracağız. Geriye doğru yaylımı açıklamak için şöyle bir sinir ağını kullanabiliriz: - Sinir ağımızda x1 ve x2 biçiminde iki girdi (özellik) vardır. - Sinir ağımızda iki saklı katman bir çıkış katmanı vardır. Bu katmanlarda tek bir nöron bulunmaktadır. - Saklı katmanlarda "RELU" aktivasyon fonksiyonu çıktı katmanında da "linear" "aktivasyon fonksiyonu kullanılmaktadır. - Bir regresyon ağı olduğu için loss fonksiyonu "mean squared error" biçimindedir: L = (y_hat - y)^2 / N Ağın görünümü şöyledir: x1 -------> Nöron --------> (h1) ---------> Nöron (h2) --------> Nöron -------> y_hat x2 -------> Burada ilk katmandaki nöronda bulunan w değerlerini w11 ve w12 olarak, bias değerini de b1 olarak ifade edersek birinci nöronun çıktısı şöyle olur: h1 = relu1(w11x1 + w12 x2 + b1) İkinci nöronun tek girdisi olduğuna göre bu nöronda bir tane w değeri olacaktır. Bu w değerini de w21 ile ve bu nörondaki bias değerini de b2 temsil edersek ikinci nöronun çıktısı şöyle olur: h2 = relu2(w21h1 + b2) Aynı durum çıktı nöronu için de uygulanırsa çıktı değeri (y_hat) şöyle olacaktır: y_hat = w31h2 + b3 Loss fonksiyonunu anımsayınız: L = (y_hat - y)^2 Bu karesel ifadeyi açalım: L = (y_hat^2 - 2y_haty + y^2)^2 Şimdi bizim amacağımız her w değerinin ve bias değerinin gradyanını bulmaktır. Bunun için parçalı türevler alınmalıdır. Gradient descent yöntemi anımsayınız: xnew = xold - grad(xold) Burada örneğin biz w11 için parçalı türev almak isteyelim. Yani ∂L/∂w11 parçalı türevini hesaplamak isteyelim. Bu parçalı türev aslında nümerik olarak hesaplanabilir. Ancak w ve bias değerlerinin bu biçimde nümerik hesaplanması oldukça işlem yükü gerektirmektedir. Nümerik hesabın işlem yükü fazla olsa da aslında mantıksal bakımdan daha kolay anlaşılmaktadır. Örneğin biz w11'e göre parçalı türev alacaksak aslında ağda yalnızca w11'de çok küçük bir değişim yaratıp loss fonksiyonun değerine bakabiliriz: ∂L/∂w11 = (L(x1, x2, w11, w12, w21, w31, b1, b2, b3) - L(x1, x2, w11 + h, w12, w21, w31, b1, b2, b3)) / h Tabii biz aslında NumPy'ın vektörel işlem yapma yeteneğini kullanarak tüm gradyenleri tek hamlede bulabiliriz. Aşağıda "claude.ai" tarafından uygun prompt girilerek yzılmış olan programı veriyoruz. #---------------------------------------------------------------------------------------------------------------------------- import numpy as np import matplotlib.pyplot as plt class SimpleNeuralNetwork: def __init__(self): # Parametreleri başlat (rastgele değerlerle) # [w1_1, w1_2, b1, w2, b2, w3, b3] # İlk katman: 2 giriş, 1 çıkış (w1_1, w1_2, b1) # İkinci katman: 1 giriş, 1 çıkış (w2, b2) # Çıkış katmanı: 1 giriş, 1 çıkış (w3, b3) self.params = np.random.randn(7) * 0.1 self.param_names = ['w1_1', 'w1_2', 'b1', 'w2', 'b2', 'w3', 'b3'] def relu(self, x): return np.maximum(0, x) def forward(self, x): z1 = self.params[0] * x[0] + self.params[1] * x[1] + self.params[2] a1 = self.relu(z1) z2 = self.params[3] * a1 + self.params[4] a2 = self.relu(z2) z3 = self.params[5] * a2 + self.params[6] return z3 def loss(self, x, y_true): y_pred = self.forward(x) return (y_pred - y_true) ** 2 def compute_numeric_gradients(self, x, y_true, epsilon=1e-6): gradients = np.zeros_like(self.params) original_loss = self.loss(x, y_true) for i in range(len(self.params)): # Parametreyi küçük bir değer kadar artır self.params[i] += epsilon new_loss = self.loss(x, y_true) gradients[i] = (new_loss - original_loss) / epsilon self.params[i] -= epsilon return gradients def train(self, X, y, learning_rate=0.01, epochs=1000): loss_history = [] for epoch in range(epochs): epoch_loss = 0 for i in range(len(X)): x = X[i] y_true = y[i] gradients = self.compute_numeric_gradients(x, y_true) self.params -= learning_rate * gradients epoch_loss += self.loss(x, y_true) avg_loss = epoch_loss / len(X) loss_history.append(avg_loss) if epoch % 100 == 0: print(f"Epoch {epoch}, Loss: {avg_loss:.4f}") return loss_history np.random.seed(42) n_samples = 100 X = np.random.randn(n_samples, 2) y = 2 * X[:, 0] - 3 * X[:, 1] + np.random.randn(n_samples) * 0.5 nn = SimpleNeuralNetwork() loss_history = nn.train(X, y, learning_rate=0.0001, epochs=1000) plt.figure(figsize=(10, 6)) plt.plot(loss_history) plt.title('Eğitim İlerlemesi') plt.xlabel('Epoch') plt.ylabel('Loss') plt.grid(True) print("\nEğitilmiş Parametreler:") for name, value in zip(nn.param_names, nn.params): print(f"{name}: {value:.4f}") test_samples = 5 test_indices = np.random.choice(len(X), test_samples, replace=False) print("\nTest Tahminleri:") for i in test_indices: pred = nn.forward(X[i]) actual = y[i] print(f"Girdi: {X[i]}, Tahmin: {pred:.4f}, Gerçek: {actual:.4f}") #---------------------------------------------------------------------------------------------------------------------------- Sinir ağındaki w ve bias değerlerinin yukarıdaki yöntemle güncellenmesi eğitimi çok yavaşlatır. w ve bias bias değerlerinin gradyenleri bileşke fonksiyonların türevlerinden faydalanılarak doğrudan hesaplanmaktadır. Yukarıdaki küçük ağımızda girdiden itibaren loss değerinin elde edilmesine kadar geçen aşamaları yeniden aşağıda veriyoruz: x1 -------> Nöron --------> (h1) ---------> Nöron (h2) --------> Nöron -------> y_hat x2 -------> h1 = relu1(w11x1 + w12 x2 + b1) h2 = relu2(w21h1 + b2) y_hat = w31h2 + b3 L = (y_hat^2 - 2y_haty + y^2)^2 Şimdi biz örneğin biz ∂L/∂w11 parçalı türevini hesaplamak isteyelim. Bileşke fonksiyonların türevleri için kullanılan zincir kuralını uygulayarak bu parçalı türevini oluşturalım: ∂L/∂w11 = ∂L/∂y_hat * ∂y_hat/∂wh2 * ∂h2/∂relu2 * ∂hrelu2/∂h1 * ∂h1/∂relu1 * ∂relu1/∂w11 #---------------------------------------------------------------------------------------------------------------------------- BURADA KALDIK #---------------------------------------------------------------------------------------------------------------------------- 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 - İş sıralama ve çizelgeleme - Çok amaçlı programalama 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'un 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 Prices" 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ı ağı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 Prices 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 Prices" 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ı açı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ğerini 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 açı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 Prices" 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)) # #---------------------------------------------------------------------------------------------------------------------------- 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 Prices" 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 Prices 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 Prices 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 Prices 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 Prices 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 Prices ö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 2008’de kurulmuştur. Aslında diğer platformlarda olan servislerin tamamen benzeri GCP’de bulunmaktadır. GCP’ye erişmek için bir Google hesabının açılmış olması gerekir. GCP’nin 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 #---------------------------------------------------------------------------------------------------------------------------- #---------------------------------------------------------------------------------------------------------------------------- GCP’de 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. #---------------------------------------------------------------------------------------------------------------------------- #---------------------------------------------------------------------------------------------------------------------------- GCP’nin -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ı AWS’de 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. Bucket’e verilecek isim yine AWS’de 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şlem 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 SageMaker’da 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 AWS’de 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ı açılmalıdır. Azure hesabı açı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 #---------------------------------------------------------------------------------------------------------------------------- #---------------------------------------------------------------------------------------------------------------------------- #---------------------------------------------------------------------------------------------------------------------------- #---------------------------------------------------------------------------------------------------------------------------- #---------------------------------------------------------------------------------------------------------------------------- #---------------------------------------------------------------------------------------------------------------------------- #---------------------------------------------------------------------------------------------------------------------------- #---------------------------------------------------------------------------------------------------------------------------- #---------------------------------------------------------------------------------------------------------------------------- #---------------------------------------------------------------------------------------------------------------------------- #---------------------------------------------------------------------------------------------------------------------------- #---------------------------------------------------------------------------------------------------------------------------- #---------------------------------------------------------------------------------------------------------------------------- #---------------------------------------------------------------------------------------------------------------------------- #---------------------------------------------------------------------------------------------------------------------------- #----------------------------------------------------------------------------------------------------------------------------