Tentang menerapkan perpustakaan pembelajaran yang mendalam di Python

Teknologi pembelajaran yang dalam telah berjalan jauh dalam waktu singkat - mulai dari jaringan saraf sederhana hingga arsitektur yang cukup kompleks. Untuk mendukung penyebaran cepat teknologi ini, berbagai perpustakaan dan platform pembelajaran yang mendalam telah dikembangkan. Salah satu tujuan utama dari perpustakaan tersebut adalah untuk menyediakan pengembang dengan antarmuka sederhana untuk membuat dan melatih model jaringan saraf. Perpustakaan semacam itu memungkinkan penggunanya untuk lebih memperhatikan tugas-tugas yang diselesaikan, dan bukan pada seluk-beluk implementasi model. Untuk melakukan ini, Anda mungkin perlu menyembunyikan implementasi mekanisme dasar di balik beberapa level abstraksi. Dan ini, pada gilirannya, memperumit pemahaman prinsip-prinsip dasar yang menjadi dasar perpustakaan pembelajaran mendalam.



Artikel, terjemahan yang kami terbitkan, bertujuan menganalisis fitur perangkat blok bangunan tingkat rendah dari perpustakaan pembelajaran yang mendalam. Pertama, kita secara singkat berbicara tentang esensi pembelajaran yang mendalam. Ini akan memungkinkan kami untuk memahami persyaratan fungsional untuk masing-masing perangkat lunak. Kemudian kita melihat mengembangkan perpustakaan pembelajaran yang dalam tapi sederhana dengan Python menggunakan NumPy. Perpustakaan ini mampu memberikan pelatihan ujung ke ujung untuk model jaringan saraf sederhana. Sepanjang jalan, kita akan berbicara tentang berbagai komponen kerangka pembelajaran yang mendalam. Perpustakaan yang akan kami pertimbangkan cukup kecil, kurang dari 100 baris kode. Dan ini berarti cukup mudah untuk mengetahuinya. Kode proyek lengkap, yang akan kami tangani, dapat ditemukan di sini .

Informasi Umum


Biasanya, perpustakaan pembelajaran yang mendalam (seperti TensorFlow dan PyTorch) terdiri dari komponen yang ditunjukkan pada gambar berikut.


Komponen kerangka kerja pembelajaran dalam

Mari kita menganalisis komponen ini.

▍ Operator


Konsep "operator" dan "lapisan" (layer) biasanya digunakan secara bergantian. Ini adalah blok bangunan dasar dari setiap jaringan saraf. Operator adalah fungsi vektor yang mengubah data. Di antara operator yang sering digunakan, seseorang dapat membedakan seperti lapisan linier dan konvolusi, lapisan sub-sampling (penyatuan), semi-linear (ReLU) dan fungsi aktivasi sigmoid (sigmoid).

▍Produk (pengoptimal)


Pengoptimal adalah dasar dari perpustakaan pembelajaran yang mendalam. Mereka menggambarkan metode untuk menyesuaikan parameter model menggunakan kriteria tertentu dan mempertimbangkan tujuan optimasi. Di antara pengoptimal terkenal, SGD, RMSProp dan Adam dapat dicatat.

▍ Fungsi kerugian


Kehilangan fungsi adalah ekspresi matematika analitik dan terdiferensiasi yang digunakan sebagai pengganti untuk tujuan optimasi ketika memecahkan masalah. Sebagai contoh, fungsi cross-entropy dan fungsi linear piecewise biasanya digunakan dalam masalah klasifikasi.

▍ Inisialisasi


Inisialisasi menyediakan nilai awal untuk parameter model. Nilai-nilai inilah yang dimiliki parameter pada awal pelatihan. Inisialisasi memainkan peran penting dalam pelatihan jaringan saraf, karena parameter awal yang tidak berhasil dapat berarti bahwa jaringan akan belajar secara lambat atau mungkin tidak belajar sama sekali. Ada banyak cara untuk menginisialisasi bobot jaringan saraf. Misalnya - Anda dapat menetapkan nilai acak kecil dari distribusi normal. Berikut adalah halaman di mana Anda dapat mempelajari tentang berbagai jenis inisialisasi.

▍ Pembuat peraturan


Regulator adalah alat yang menghindari pelatihan ulang jaringan dan membantu jaringan mendapatkan kemampuan generalisasi. Anda dapat menangani pelatihan ulang jaringan secara eksplisit atau implisit. Metode eksplisit melibatkan batasan struktural pada bobot. Misalnya, meminimalkan L1-Norm dan L2-Norm, yang, masing-masing, membuat nilai bobot lebih tersebar dan lebih merata. Metode implisit diwakili oleh operator khusus yang melakukan transformasi representasi menengah. Ini dilakukan baik dengan normalisasi eksplisit, misalnya, menggunakan teknik BatchNorm, atau dengan mengubah konektivitas jaringan menggunakan algoritma DropOut dan DropConnect.

Komponen di atas biasanya milik bagian antarmuka perpustakaan. Di sini, dengan "bagian antarmuka" yang saya maksud entitas yang berinteraksi dengan pengguna. Mereka memberinya alat yang nyaman untuk merancang arsitektur jaringan saraf secara efisien. Jika kita berbicara tentang mekanisme internal perpustakaan, mereka dapat memberikan dukungan untuk perhitungan gradien fungsi kerugian secara otomatis, dengan mempertimbangkan berbagai parameter model. Teknik ini biasa disebut Automatic Differentiation (AD).

Diferensiasi otomatis


Setiap pustaka pembelajaran yang dalam memberikan pengguna dengan beberapa kemampuan diferensiasi otomatis. Ini memberinya kesempatan untuk fokus pada deskripsi struktur model (grafik perhitungan) dan mentransfer tugas menghitung gradien ke modul AD. Mari kita ambil contoh yang akan memberi tahu kami cara kerjanya. Misalkan kita ingin menghitung turunan parsial dari fungsi berikut sehubungan dengan variabel input X₁ dan Xβ‚‚:

Y = sin (x₁) + X₁ * Xβ‚‚

Gambar berikut, yang saya pinjam dari sini , menunjukkan grafik perhitungan dan perhitungan turunan menggunakan aturan rantai.


Grafik komputasi dan perhitungan turunan oleh aturan rantai

Apa yang Anda lihat di sini adalah sesuatu seperti "mode terbalik" diferensiasi otomatis. Algoritme propagasi kesalahan kembali terkenal adalah kasus khusus dari algoritma di atas untuk kasus di mana fungsi yang terletak di atas adalah fungsi kerugian. AD mengeksploitasi fakta bahwa setiap fungsi kompleks terdiri dari operasi aritmatika elementer dan fungsi elementer. Akibatnya, turunan dapat dihitung dengan menerapkan aturan rantai pada operasi ini.

Penerapan


Pada bagian sebelumnya, kami memeriksa komponen yang diperlukan untuk membuat perpustakaan pembelajaran yang mendalam yang dirancang untuk membuat dan pelatihan ujung-ke-ujung dari jaringan saraf. Agar tidak menyulitkan contoh, saya meniru pola desain perpustakaan Caffe di sini . Di sini kita mendeklarasikan dua kelas abstrak - Functiondan Optimizer. Selain itu, ada kelas Tensor, yang merupakan struktur sederhana yang berisi dua array NumPy multidimensi. Salah satunya dirancang untuk menyimpan nilai parameter, yang lain - untuk menyimpan gradien mereka. Semua parameter di lapisan yang berbeda (operator) akan bertipe Tensor. Sebelum kita melangkah lebih jauh, lihat garis besar umum perpustakaan.


Diagram UML Perpustakaan

Pada saat penulisan materi ini, perpustakaan ini berisi implementasi dari lapisan linier, fungsi aktivasi ReLU, lapisan SoftMaxLoss dan pengoptimal SGD. Akibatnya, ternyata perpustakaan dapat digunakan untuk melatih model klasifikasi yang terdiri dari lapisan yang terhubung penuh dan menggunakan fungsi aktivasi nonlinier. Sekarang mari kita lihat beberapa detail tentang kelas abstrak yang kita miliki.

Kelas abstrakFunctionmenyediakan antarmuka untuk operator. Ini kodenya:

class  Function(object):
    def forward(self): 
        raise NotImplementedError
    
    def backward(self): 
        raise NotImplementedError
    
    def getParams(self): 
        return []

Semua operator diimplementasikan melalui pewarisan kelas abstrak Function. Setiap operator harus menyediakan implementasi metode forward()dan backward(). Operator dapat berisi implementasi metode opsional getParams()yang mengembalikan parameternya (jika ada). Metode forward()menerima data input dan mengembalikan hasil transformasi mereka oleh operator. Selain itu, ia memecahkan masalah internal yang diperlukan untuk menghitung gradien. Metode ini backward()menerima turunan parsial dari fungsi kehilangan sehubungan dengan output operator dan mengimplementasikan perhitungan derivatif parsial dari fungsi kehilangan sehubungan dengan data input operator dan parameter (jika ada). Perhatikan metode itubackward(), pada dasarnya, menyediakan perpustakaan kami dengan kemampuan untuk melakukan diferensiasi otomatis.

Untuk menangani semua ini dengan contoh spesifik, mari kita lihat implementasi fungsi Linear:

class Linear(Function):
    def __init__(self,in_nodes,out_nodes):
        self.weights = Tensor((in_nodes,out_nodes))
        self.bias    = Tensor((1,out_nodes))
        self.type = 'linear'

    def forward(self,x):
        output = np.dot(x,self.weights.data)+self.bias.data
        self.input = x 
        return output

    def backward(self,d_y):
        self.weights.grad += np.dot(self.input.T,d_y)
        self.bias.grad    += np.sum(d_y,axis=0,keepdims=True)
        grad_input         = np.dot(d_y,self.weights.data.T)
        return grad_input

    def getParams(self):
        return [self.weights,self.bias]

Metode forward()mengimplementasikan transformasi tampilan Y = X*W+bdan mengembalikan hasilnya. Selain itu, menghemat nilai input X, karena diperlukan untuk menghitung turunan parsial dari dYfungsi kerugian sehubungan dengan nilai output Ydalam metode ini backward(). Metode backward()menerima turunan parsial, dihitung sehubungan dengan nilai input Xdan parameter Wdan b. Selain itu, ia mengembalikan turunan parsial yang dihitung sehubungan dengan nilai input X, yang akan ditransfer ke lapisan sebelumnya.

Kelas abstrak Optimizermenyediakan antarmuka untuk pengoptimal:

class Optimizer(object):
    def __init__(self,parameters):
        self.parameters = parameters
    
    def step(self): 
        raise NotImplementedError

    def zeroGrad(self):
        for p in self.parameters:
            p.grad = 0.

Semua pengoptimal diterapkan dengan mewarisi dari kelas dasar Optimizer. Kelas yang menjelaskan optimasi tertentu harus menyediakan implementasi metode step(). Metode ini memperbarui parameter model menggunakan turunan parsial yang dihitung dalam kaitannya dengan nilai optimal dari fungsi kerugian. Tautan ke berbagai parameter model disediakan dalam fungsi __init__(). Harap dicatat bahwa fungsi universal untuk mengatur ulang nilai gradien diimplementasikan di kelas dasar itu sendiri.

Sekarang, untuk lebih memahami semua ini, pertimbangkan contoh khusus - implementasi algoritma stochastic gradient descent (SGD) dengan dukungan untuk menyesuaikan momentum dan mengurangi bobot:

class SGD(Optimizer):
    def __init__(self,parameters,lr=.001,weight_decay=0.0,momentum = .9):
        super().__init__(parameters)
        self.lr           = lr
        self.weight_decay = weight_decay
        self.momentum     = momentum
        self.velocity     = []
        for p in parameters:
            self.velocity.append(np.zeros_like(p.grad))

    def step(self):
        for p,v in zip(self.parameters,self.velocity):
            v = self.momentum*v+p.grad+self.weight_decay*p.data
            p.data=p.data-self.lr*v

Solusi untuk masalah nyata


Sekarang kami memiliki semua yang kami butuhkan untuk melatih model jaringan saraf (dalam) menggunakan perpustakaan kami. Untuk ini kita membutuhkan entitas berikut:

  • Model: grafik perhitungan.
  • Data dan nilai target: data untuk pelatihan jaringan.
  • Fungsi kerugian: pengganti tujuan pengoptimalan.
  • Pengoptimal: mekanisme untuk memperbarui parameter model.

Kode pseudo berikut menjelaskan siklus pengujian yang khas:

model # 
data,target # 
loss_fn # 
optim #,         
Repeat:#   ,    ,     
   optim.zeroGrad() #    
   output = model.forward(data) #   
   loss   = loss_fn(output,target) # 
   grad   = loss.backward() #      
   model.backward(grad) #    
   optim.step() #  

Meskipun ini tidak diperlukan dalam perpustakaan pembelajaran yang mendalam, mungkin berguna untuk memasukkan fungsi di atas dalam kelas yang terpisah. Ini akan memungkinkan kita untuk tidak mengulangi tindakan yang sama ketika mempelajari model baru (ide ini sesuai dengan filosofi abstraksi kerangka kerja tingkat tinggi seperti Keras ). Untuk mencapai ini, deklarasikan kelas Model:

class Model():
    def __init__(self):
        self.computation_graph = []
        self.parameters        = []

    def add(self,layer):
        self.computation_graph.append(layer)
        self.parameters+=layer.getParams()

    def __innitializeNetwork(self):
        for f in self.computation_graph:
            if f.type=='linear':
                weights,bias = f.getParams()
                weights.data = .01*np.random.randn(weights.data.shape[0],weights.data.shape[1])
                bias.data    = 0.

    def fit(self,data,target,batch_size,num_epochs,optimizer,loss_fn):
        loss_history = []
        self.__innitializeNetwork()
        data_gen = DataGenerator(data,target,batch_size)
        itr = 0
        for epoch in range(num_epochs):
            for X,Y in data_gen:
                optimizer.zeroGrad()
                for f in self.computation_graph: X=f.forward(X)
                loss = loss_fn.forward(X,Y)
                grad = loss_fn.backward()
                for f in self.computation_graph[::-1]: grad = f.backward(grad) 
                loss_history+=[loss]
                print("Loss at epoch = {} and iteration = {}: {}".format(epoch,itr,loss_history[-1]))
                itr+=1
                optimizer.step()
        
        return loss_history
    
    def predict(self,data):
        X = data
        for f in self.computation_graph: X = f.forward(X)
        return X

Kelas ini mencakup fungsionalitas berikut:

  • : add() , . computation_graph.
  • : , , , .
  • : fit() . , .
  • : predict() , , .

Karena kelas ini bukan blok bangunan dasar dari sistem pembelajaran yang mendalam, saya mengimplementasikannya dalam modul terpisah utilities.py. Perhatikan bahwa metode ini fit()menggunakan kelas DataGeneratoryang implementasinya dalam modul yang sama. Kelas ini hanya pembungkus untuk data pelatihan dan menghasilkan paket mini untuk setiap iterasi pelatihan.

Pelatihan model


Sekarang perhatikan potongan kode terakhir di mana model jaringan saraf dilatih menggunakan perpustakaan yang dijelaskan di atas. Saya akan melatih jaringan multilayer pada data yang disusun secara spiral. Saya diminta oleh publikasi ini . Kode untuk menghasilkan data ini dan untuk memvisualisasikannya dapat ditemukan dalam file utilities.py.


Data dengan tiga kelas disusun dalam bentuk spiral.Gambar 

sebelumnya menunjukkan visualisasi data yang akan kita latih modelnya. Data ini dapat dipisahkan secara nonlinier. Kita dapat berharap bahwa jaringan dengan lapisan tersembunyi dapat dengan benar menemukan batas keputusan nonlinier. Jika Anda mengumpulkan semua yang kami bicarakan, Anda mendapatkan fragmen kode berikut yang memungkinkan Anda untuk melatih model:

import dl_numpy as DL
import utilities

batch_size        = 20
num_epochs        = 200
samples_per_class = 100
num_classes       = 3
hidden_units      = 100
data,target       = utilities.genSpiralData(samples_per_class,num_classes)
model             = utilities.Model()
model.add(DL.Linear(2,hidden_units))
model.add(DL.ReLU())
model.add(DL.Linear(hidden_units,num_classes))
optim   = DL.SGD(model.parameters,lr=1.0,weight_decay=0.001,momentum=.9)
loss_fn = DL.SoftmaxWithLoss()
model.fit(data,target,batch_size,num_epochs,optim,loss_fn)
predicted_labels = np.argmax(model.predict(data),axis=1)
accuracy         = np.sum(predicted_labels==target)/len(target)
print("Model Accuracy = {}".format(accuracy))
utilities.plot2DDataWithDecisionBoundary(data,target,model)

Gambar di bawah ini menunjukkan data yang sama dan batas-batas yang menentukan dari model yang dilatih.


Data dan batas keputusan model yang dilatih

Ringkasan


Mengingat semakin kompleksnya model pembelajaran yang mendalam, ada kecenderungan untuk meningkatkan kemampuan perpustakaan masing-masing dan untuk meningkatkan jumlah kode yang diperlukan untuk mengimplementasikan kemampuan ini. Tetapi fungsi paling dasar dari perpustakaan tersebut masih dapat diimplementasikan dalam bentuk yang relatif kompak. Meskipun perpustakaan yang kami buat dapat digunakan untuk pelatihan jaringan sederhana end-to-end, masih, dalam banyak hal, masih terbatas. Kami berbicara tentang keterbatasan di bidang kemampuan yang memungkinkan kerangka kerja pembelajaran yang mendalam untuk digunakan di berbagai bidang seperti visi mesin, ucapan dan pengenalan teks. Ini, tentu saja, kemungkinan kerangka kerja seperti itu tidak terbatas.

Saya percaya bahwa setiap orang dapat membayar proyek, kode yang kami periksa di sini, dan, sebagai latihan, perkenalkan ke dalamnya apa yang ingin mereka lihat di dalamnya. Berikut adalah beberapa mekanisme yang dapat Anda coba terapkan sendiri:

  • Operator: konvolusi, subsampling.
  • Pengoptimal: Adam, RMSProp.
  • Regulator: BatchNorm, DropOut.

Saya berharap materi ini memungkinkan Anda untuk setidaknya melihat dari sudut mata Anda apa yang terjadi di perut perpustakaan untuk pembelajaran yang mendalam.

Pembaca yang budiman! Perpustakaan pembelajaran mendalam apa yang Anda gunakan?

Source: https://habr.com/ru/post/undefined/


All Articles