paint-brush
Python, Sinir Ağları ve Kütüphaneler Olmadan Swift'de ML Görevleriby@pichukov
552
552

Python, Sinir Ağları ve Kütüphaneler Olmadan Swift'de ML Görevleri

Aleksei Pichukov15m2024/01/04
Read on Terminal Reader

Bu makale Swift'de bir şeyin nasıl yazılacağına dair bir rehber değildir; daha ziyade, Python'u, kullandıkları dil ne olursa olsun, karşılaştıkları herhangi bir sorunu veya görevi çözecek makine öğrenimi kitaplıkları için nihai çözüme giden bir köprü olarak gören birçok geliştiricinin mevcut zihniyetiyle ilgili bir düşünce parçası gibidir. Çoğu geliştiricinin, onlarsız alternatif çözümler düşünmek yerine, zamanlarını Python kitaplıklarını kendi dillerine/ortamlarına entegre etmenin yollarını bulmaya harcamayı tercih edeceğine bahse girerim. Bu doğası gereği kötü olmasa da (yeniden kullanım son birkaç on yıldır BT'deki ilerlemenin önemli bir itici gücü olmuştur) birçok geliştiricinin artık alternatif çözümleri dikkate almadığını hissetmeye başladım. Bu zihniyet, mevcut durum ve Büyük Dil Modellerindeki ilerlemelerle birlikte daha da yerleşik hale geliyor. Klasik bir ML görevini alıp Swift dilini kullanarak ve Kütüphaneler olmadan çözeceğiz.
featured image - Python, Sinir Ağları ve Kütüphaneler Olmadan Swift'de ML Görevleri
Aleksei Pichukov HackerNoon profile picture
0-item
1-item

Sinir ağları bugün Makine Öğreniminin (ML) ön saflarında yer alıyor ve Python, herhangi bir makine öğrenimi görevi için, kişinin bunu çözmek için Sinir Ağlarını kullanmayı düşünüp düşünmediğine bakılmaksızın, şüphesiz en uygun programlama dilidir. NumPy, Pandas, Keras, TensorFlow, PyTorch ve benzeri gibi makine öğrenimi görevlerinin tüm yelpazesini kapsayan çok çeşitli Python kitaplıkları mevcuttur. Bu kütüphaneler genellikle makine öğrenimi algoritmalarının C veya C++ uygulamalarına ve temel yaklaşımlara dayanır çünkü Python onlar için çok yavaştır. Ancak Python var olan tek programlama dili değil ve günlük işlerimde kullandığım dil de değil.


Bu makale Swift'de bir şeyin nasıl yazılacağına dair bir rehber değildir; daha ziyade, Python'u, kullandıkları dil ne olursa olsun, karşılaştıkları herhangi bir sorunu veya görevi çözecek makine öğrenimi kitaplıkları için nihai çözüme giden bir köprü olarak gören birçok geliştiricinin mevcut zihniyetiyle ilgili bir düşünce parçası gibidir. Çoğu geliştiricinin, onlarsız alternatif çözümler düşünmek yerine Python kitaplıklarını kendi dillerine/ortamlarına entegre etmenin yollarını bulmaya zaman ayırmayı tercih edeceğine bahse girerim. Bu doğası gereği kötü olmasa da (yeniden kullanım son birkaç on yıldır BT'deki ilerlemenin önemli bir itici gücü olmuştur) birçok geliştiricinin artık alternatif çözümleri dikkate almadığını hissetmeye başladım. Bu zihniyet, mevcut durum ve Büyük Dil Modellerindeki ilerlemelerle birlikte daha da yerleşik hale geliyor.


Denge eksik; Yüksek Lisans'lardan sorunlarımızı çözmelerini istemek, bazı Python kodları almak, kopyalamak ve gereksiz bağımlılıklardan kaynaklanan potansiyel olarak önemli miktarda ek yük ile üretkenliğimizin tadını çıkarmak için acele ediyoruz.


Eldeki görevi yalnızca Swift, matematik kullanarak ve başka hiçbir araç kullanmadan çözmek için alternatif yaklaşımı keşfedelim.


İnsanlar Sinir Ağlarını öğrenmeye başladığında, çoğu eğitimde ve giriş materyalinde bulabileceğiniz iki klasik Merhaba Dünya örneği vardır. Bunlardan ilki el yazısı rakam tanımadır. İkincisi veri sınıflandırmasıdır. Bu yazıda ikincisine odaklanacağım ama uygulayacağım çözüm birincisi için de işe yarayacak.


Bunun çok iyi bir görsel örneği, farklı sinir ağı yapılarıyla oynayabileceğiniz ve ortaya çıkan modelin görevi ne kadar iyi çözdüğünü görsel olarak gözlemleyebileceğiniz TensorFlow Playground'da bulunabilir.


TensorFlow Oyun Alanı örneği


Farklı renklere sahip bir görüntüdeki bu noktaların pratik anlamının ne olduğunu sorabilirsiniz. Mesele şu ki, bu bazı veri kümelerinin görsel bir temsilidir. Belirli ürünleri satın alan sosyal gruplar veya müzik tercihleri gibi birçok farklı türde veriyi tamamen aynı veya benzer şekilde sunabilirsiniz. Öncelikle mobil iOS geliştirmeye odaklandığım için, çözmekte olduğum ve benzer şekilde görsel olarak temsil edilebilecek gerçek bir göreve bir örnek de vereceğim: bir cep telefonundaki jiroskop ve manyetometre kullanarak duvarların içindeki elektrik kablolarını bulmak. Bu özel örnekte, bulunan tel ile ilgili bir dizi parametremiz var ve duvarın içinde hiçbir şey için olmayan başka bir parametre kümesi var.


Kullanacağımız verilere bir göz atalım.

ML-Testi


Burada iki tür verimiz var: kırmızı noktalar ve mavi noktalar. Yukarıda anlattığım gibi her türlü gizli verinin görsel temsili olabilir. Örneğin duvarda elektrik teli olduğu durumlarda manyetometre ve jiroskoptan sinyal aldığımız alanı kırmızı alanı, olmadığı durumda ise mavi alanı ele alalım.


Bu noktaların bir şekilde bir araya gelerek bir çeşit kırmızı ve mavi şekiller oluşturduğunu görebiliriz. Bu noktaların oluşturulma şekli, aşağıdaki görüntüden rastgele noktaların alınmasıdır:


Noktalar birlikte gruplandırılmış


Bu resmi, modeli eğitmek için rastgele noktalar ve eğitilen modelimizi test etmek için diğer rastgele noktaları alarak tren sürecimiz için rastgele bir model olarak kullanacağız.


Orijinal resim 300 x 300 pikseldir ve 90.000 nokta (nokta) içerir. Eğitim amacıyla bu noktaların yalnızca %0,2'sini kullanacağız, bu da 100 puandan azdır. Modelin performansını daha iyi anlayabilmek için resim üzerinde rastgele 3000 nokta seçeceğiz ve etraflarına daireler çizeceğiz. Bu görsel sunum bize sonuçlar hakkında daha kapsamlı bir fikir verecektir. Modelin verimliliğini doğrulamak için doğruluk yüzdesini de ölçebiliriz.


Nasıl model yapacağız? Bu iki resme birlikte bakarsak ve görevimizi basitleştirmeye çalışırsak, aslında görevin, elimizdeki verilerden (kırmızı ve mavi noktalardan oluşan bir grup) Origin resmini yeniden oluşturmak olduğunu anlarız. Ve modelimizden elde ettiğimiz resim orijinaline ne kadar yakınsa, modelimizin çalışması da o kadar doğru olacaktır. Ayrıca test verilerimizi orijinal görüntümüzün aşırı derecede sıkıştırılmış bir versiyonu olarak da düşünebiliriz ve sıkıştırmayı geri açma hedefimiz olabilir.


Yapacağımız şey, noktalarımızı kodda diziler veya vektörler olarak temsil edilecek matematiksel fonksiyonlara dönüştürmektir (burada metinde vektör terimini kullanacağım çünkü matematik dünyasındaki fonksiyon ile yazılım geliştirmedeki dizi arasında yer alıyor). Daha sonra bu vektörleri her test noktasına meydan okumak ve hangi vektöre daha çok ait olduğunu belirlemek için kullanacağız.


Verilerimizi dönüştürmek için Ayrık Kosinüs Dönüşümünü (DCT) deneyeceğim. Ne olduğu ve nasıl çalıştığına dair herhangi bir matematiksel açıklamaya girmeyeceğim, dilerseniz bu bilgilere kolaylıkla ulaşabilirsiniz. Ancak bize nasıl yardımcı olabileceğini ve neden faydalı olduğunu basit terimlerle açıklayabilirim. DCT, görüntü sıkıştırma (JPEG formatı gibi) dahil olmak üzere birçok alanda kullanılır. Önemsiz ayrıntıları çıkarırken görüntünün yalnızca önemli kısımlarını tutarak verileri daha kompakt bir formata dönüştürür. DCT'yi yalnızca kırmızı noktalar içeren 300x300 boyutundaki görüntümüze uygularsak, her satırı ayrı ayrı alarak bir diziye (veya vektöre) dönüştürülebilen 300x300'lük bir değerler matrisi elde ederiz.


Son olarak bunun için bir kod yazalım. Öncelikle noktamızı (nokta) temsil edecek basit bir nesne oluşturmamız gerekiyor.


 enum Category { case red case blue case none } struct Point: Hashable { let x: Int let y: Int let category: Category }


none adında ek bir kategorimizin olduğunu fark edebilirsiniz. Sonunda aslında üç vektör yaratacağız: biri red noktalar için, ikincisi blue noktalar için ve üçüncüsü none ile temsil edilen herhangi bir şey için. Bunlardan sadece iki tanesine sahip olabilsek de, kırmızı ve mavi olmayanlar için eğitilmiş bir vektöre sahip olmak işleri biraz daha kolaylaştıracaktır.


Test vektörümüzde aynı koordinatlara sahip noktaların olmasını önlemek için bir Set kullanmak üzere Hashable protokolüne uygun 'Nokta'ya sahibiz.


 func randomPoints(from points: [Point], percentage: Double) -> [Point] { let count = Int(Double(points.count) * percentage) var result = Set<Point>() while result.count < count { let index = Int.random(in: 0 ..< points.count) result.insert(points[index]) } return Array<Point>(result) }


Artık bunu orijinal görüntümüzden kırmızı, mavi ve yok noktaları için 0.2% rastgele puan almak için kullanabiliriz.


 redTrainPoints = randomPoints(from: redPoints, percentage: 0.002) blueTrainPoints = randomPoints(from: bluePoints, percentage: 0.002) noneTrainPoints = randomPoints(from: nonePoints, percentage: 0.002)


Bu eğitim verilerini DCT kullanarak dönüştürmeye hazırız. İşte bunun bir uygulaması:


 final class CosTransform { private var sqrtWidthFactorForZero: Double = 0 private var sqrtWidthFactorForNotZero: Double = 0 private var sqrtHeightFactorForZero: Double = 0 private var sqrtHeightFactorForNotZero: Double = 0 private let cosLimit: Int init(cosLimit: Int) { self.cosLimit = cosLimit } func discreteCosTransform(for points: [Point], width: Int, height: Int) -> [[Double]] { if sqrtWidthFactorForZero == 0 { prepareSupportData(width: width, height: height) } var result = Array(repeating: Array(repeating: Double(0), count: width), count: height) for y in 0..<height { for x in 0..<width { let cos = cosSum( points: points, width: width, height: height, x: x, y: y ) result[y][x] = cFactorHeight(index: y) * cFactorWidth(index: x) * cos } } return result } func shortArray(matrix: [[Double]]) -> [Double] { let height = matrix.count guard let width = matrix.first?.count else { return [] } var array: [Double] = [] for y in 0..<height { for x in 0..<width { if y + x <= cosLimit { array.append(matrix[y][x]) } } } return array } private func prepareSupportData(width: Int, height: Int) { sqrtWidthFactorForZero = Double(sqrt(1 / CGFloat(width))) sqrtWidthFactorForNotZero = Double(sqrt(2 / CGFloat(width))) sqrtHeightFactorForZero = Double(sqrt(1 / CGFloat(height))) sqrtHeightFactorForNotZero = Double(sqrt(2 / CGFloat(height))) } private func cFactorWidth(index: Int) -> Double { return index == 0 ? sqrtWidthFactorForZero : sqrtWidthFactorForNotZero } private func cFactorHeight(index: Int) -> Double { return index == 0 ? sqrtHeightFactorForZero : sqrtHeightFactorForNotZero } private func cosSum( points: [Point], width: Int, height: Int, x: Int, y: Int ) -> Double { var result: Double = 0 for point in points { result += cosItem(point.x, x, height) * cosItem(point.y, y, width) } return result } private func cosItem( _ firstParam: Int, _ secondParam: Int, _ lenght: Int ) -> Double { return cos((Double(2 * firstParam + 1) * Double(secondParam) * Double.pi) / Double(2 * lenght)) } }


CosTransform nesnesinin bir örneğini oluşturup test edelim.


 let math = CosTransform(cosLimit: Int.max) ... redCosArray = cosFunction(points: redTrainPoints) blueCosArray = cosFunction(points: blueTrainPoints) noneCosArray = cosFunction(points: noneTrainPoints)


Burada bazı basit yardımcı işlevleri kullanıyoruz:


 func cosFunction(points: [Point]) -> [Double] { return math.shortArray( matrix: math.discreteCosTransform( for: points, width: 300, height: 300 ) ) }


CosTransform shortArray fonksiyonu içinde kullanılan bir cosLimit parametresi var, bunun amacını daha sonra açıklayacağım, şimdilik bunu görmezden gelelim ve orijinal görüntüden 3000 rastgele noktanın sonucunu oluşturduğumuz redCosArray , blueCosArray ve noneCosArray Vektörlerimiz ile karşılaştıralım. Bunun işe yaraması için orijinal görüntüden alınan tek bir noktadan başka bir DCT vektörü oluşturmamız gerekiyor. Bunu tamamen aynı şekilde yapıyoruz ve Red , Blue ve None cos Vektörlerimiz için zaten yaptığımız işlevlerin aynısını kullanıyoruz. Peki bu yeni vektörün hangisine ait olduğunu nasıl bulabiliriz? Bunun çok basit bir matematik yaklaşımı var: Dot Product . İki Vektörü karşılaştırma ve en benzer çifti bulma görevimiz olduğundan Nokta Çarpımı bize tam olarak bunu verecektir. İki özdeş Vektör için bir Nokta Çarpım işlemi uygularsanız, bu size aynı Vektöre ve farklı değerlere sahip herhangi bir Vektöre uygulanan diğer herhangi bir Nokta Çarpım sonucundan daha büyük olacak bir pozitif değer verecektir. Ve eğer ortogonal Vektörlere (birbirleri arasında ortak hiçbir şeyi olmayan Vektörler) bir Nokta Çarpımı uygularsanız sonuç olarak 0 elde edersiniz. Bunu dikkate alarak basit bir algoritma oluşturabiliriz:


  1. 3000 rastgele noktamızın tamamını tek tek gözden geçirin.
  2. DCT (Ayrık Kosinüs Dönüşümü) kullanarak 300x300'lük bir matristen yalnızca tek bir noktaya sahip bir vektör oluşturun.
  3. Bu vektör için redCosArray ile, ardından blueCosArray ile ve ardından noneCosArray ile bir nokta çarpım uygulayın.
  4. Önceki adımın en iyi sonucu bizi doğru cevaba yönlendirecektir: Red , Blue , None .


Burada eksik olan tek işlevsellik Nokta Çarpımıdır, bunun için basit bir işlev yazalım:


 func dotProduct(_ first: [Double], _ second: [Double]) -> Double { guard first.count == second.count else { return 0 } var result: Double = 0 for i in 0..<first.count { result += first[i] * second[i] } return result }


Ve işte algoritmanın bir uygulaması:


 var count = 0 while count < 3000 { let index = Int.random(in: 0 ..< allPoints.count) let point = allPoints[index] count += 1 let testArray = math.shortArray( matrix: math.discreteCosTransform( for: [point], width: 300, height: 300 ) ) let redResult = dotProduct(redCosArray, testArray) let blueResult = dotProduct(blueCosArray, testArray) let noneResult = dotProduct(noneCosArray, testArray) var maxValue = redResult var result: Category = .red if blueResult > maxValue { maxValue = blueResult result = .blue } if noneResult > maxValue { maxValue = noneResult result = .none } fillPoints.append(Point(x: point.x, y: point.y, category: result)) }


Şimdi tek yapmamız gereken fillPoints bir resim çizmek. Kullandığımız tren noktalarına, tren verilerimizden oluşturduğumuz DCT vektörlerine ve elde ettiğimiz sonuca bir göz atalım:

Sonuç

Rastgele bir gürültüye benziyor. Şimdi vektörlerin görsel temsiline bir göz atalım. Orada bazı ani artışlar görebilirsiniz, bu tam olarak odaklanmamız gereken bilgidir ve DCT sonucumuzdaki gürültünün çoğunu ortadan kaldırır. DCT matrisinin basit görsel temsiline bir göz atarsak, en yararlı bilginin (görüntünün benzersiz özelliklerini tanımlayan bilgi) sol üst köşede yoğunlaştığını görürüz:


Konsantrasyon


Şimdi bir adım geriye gidelim ve shortArray fonksiyonunu bir kez daha kontrol edelim. Burada tam olarak DCT matrisinin sol üst köşesini almamızın ve vektörümüzü benzersiz kılan en aktif parametrelerin kullanılmasının nedeni olarak cosLimit parametresini kullanıyoruz.


 func shortArray(matrix: [[Double]]) -> [Double] { let height = matrix.count guard let width = matrix.first?.count else { return [] } var array: [Double] = [] for y in 0..<height { for x in 0..<width { if y + x <= cosLimit { array.append(matrix[y][x]) } } } return array }


math nesnemizi farklı cosLimit ile oluşturalım:


 let math = CosTransform(cosLimit: 30)


Şimdi 90.000 değerin tamamını kullanmak yerine DCT matrisinin sol üst köşesinden yalnızca 30 x 30 / 2 = 450 kullanacağız. Elde ettiğimiz sonuca bir göz atalım:

Sonuç

Gördüğünüz gibi zaten daha iyi. Ayrıca Vector'leri benzersiz kılan sivri uçların çoğunun hala ön kısımda yer aldığını görebiliyoruz (resimde yeşil ile seçildiği gibi), CosTransform(cosLimit: 6) kullanmayı deneyelim, bu da sadece 6 x 6 / 2 = 18 kullanacağımız anlamına geliyor 6 x 6 / 2 = 18 değer ve sonucu kontrol edin:

Başarı

Şimdi çok daha iyi, orijinal görüntüye çok yakın. Ancak tek bir küçük sorun var; bu uygulama yavaş. DCT'nin zaman alıcı bir işlem olduğunu anlamak için algoritma karmaşıklığı konusunda uzman olmanıza gerek yoktur, ancak doğrusal zaman karmaşıklığına sahip nokta çarpımı bile Swift dizileri kullanan büyük vektörlerle çalışırken yeterince hızlı değildir. İyi haber şu ki, halihazırda standart bir kitaplık olarak sahip olduğumuz Apple'ın Accelerate çerçevesindeki vDSP kullanarak bunu uygulamada çok daha hızlı ve daha basit yapabiliyoruz. Burada vDSP hakkında bilgi edinebilirsiniz, ancak basit bir ifadeyle, dijital sinyal işleme görevlerinin çok hızlı bir şekilde yürütülmesine yönelik bir dizi yöntemden oluşur. Büyük veri kümeleriyle mükemmel şekilde çalışan birçok düşük seviyeli optimizasyona sahiptir. Nokta çarpımımızı ve DCT'yi vDSP kullanarak uygulayalım:


 infix operator • public func •(left: [Double], right: [Double]) -> Double { return vDSP.dot(left, right) } prefix operator ->> public prefix func ->>(value: [Double]) -> [Double] { let setup = vDSP.DCT(count: value.count, transformType: .II) return setup!.transform(value.compactMap { Float($0) }).compactMap { Double($0) } }


Daha az sıkıcı hale getirmek için bazı operatörleri kullanarak daha okunabilir hale getirdim. Artık bu işlevleri aşağıdaki şekilde kullanabilirsiniz:


 let cosRedArray = ->> redValues let redResult = redCosArray • testArray


Yeni DCT uygulamasında mevcut matris boyutumuzla ilgili bir sorun var. 2'nin katları olan belirli boyutlarla çalışmak üzere optimize edildiğinden, 300 x 300 görselimizle çalışmaz. Bu nedenle, görseli yeni yönteme vermeden önce ölçeklendirmek için biraz çaba harcamamız gerekecek.

Özet

Bu metni şimdiye kadar okumayı başaran veya okumadan kaydıracak kadar tembel olan herkese teşekkürler. Bu makalenin amacı, insanların bazı yerel araçlarla çözmeyi düşünmediği pek çok görevin minimum çabayla çözülebileceğini göstermekti. Alternatif çözümler aramak keyiflidir ve bu tür görevleri çözmenin tek seçeneği olarak aklınızı Python kitaplığı entegrasyonuyla sınırlamayın.