神經網絡進階-用python實現一個完整的神經網絡框架並在CIFAR10數據集上調參


  上一個博客中講解了用python實現一個簡單的兩層神經網絡,我們是把所有的網絡層都直接寫在了類中。但是作為一個神經網絡框架,網絡的結構應該是可以由使用者自定義的,這樣一來也就不用為每個網絡結構都重寫所有代碼,我們把每一層模塊化,在神經網絡的類中定義結構時使用這些模塊化的層堆疊形成一個完整的神經網絡。每一種層,分別實現forward和password兩個函數,用來正向計算和反向傳播。

這里我們實現的網絡層和功能有:全連層、激活層、計算loss、自動訓練

1、全連層

全連層的正向計算很簡單,f(W,x)=xW+b。

反向傳播分別求x,W,b的梯度,dx=f(W,x)·W.T,dW=x·f(W,x),db=f(W,x)。

 1 def affine_forward(x, w, b):
 2     #######################################
 3     #  x: input      shape (N, d_1, ..., d_k)
 4     #  w: Weights    shape (D, M)
 5     #  b: bias       shape (M)
 6     #
 7     #  Returns :
 8     #  out: output,shape (N, M)
 9     #  cache: (x, w, b)
10     ########################################
11     x1=x.reshape(x.shape[0],-1)
12     out=np.dot(x1,w)+b
13     cache=(x,w)
14     return out,cache
15 
16 def affine_backward(dout, cache):
17     ##################################################
18     # dout: Upstream derivative, of shape (N, M)
19     # cache: Tuple of:
20     #  x: Input   shape (N, d_1, ... d_k)
21     #  w: Weights shape (D, M)
22     #
23     # Returns a tuple of:
24     #  dx: shape (N, d1, ..., d_k)
25     #  dw: shape (D, M)
26     #  db: shape (M,)
27     ##################################################
28     x,w=cache
29     N=x.shape[0]
30     x1=x.reshape(N,-1)
31     dx=np.dot(dout,w.T).reshape(*x.shape)
32     dw=np.dot(x1.T,dout)
33     db=np.sum(dout,axis=0)
34     return dx,dw,db
View Code

2、激活層

激活層只實現常用的relu激活函數。f(x)=max(0,x)。

反向傳播也很簡單,被激活的繼承上一級的梯度,沒有激活的梯度為0。這里不涉及到參數的梯度計算,只涉及到梯度的傳播。

 1 def relu_forward(x):
 2     out = np.maximum(0,x)
 3     cache = x
 4     return out, cache
 5 
 6 
 7 def relu_backward(dout, cache):
 8     x = cache
 9     dx = dout * (x>0)
10     return dx
View Code

實際使用過程中,一般全連接層后面都會接激活層,為了方便起見,可以合並兩層。這里的合並跟之后的整個神經網絡的堆疊思想一致。

 1 def affine_relu_forward(x, w, b):
 2     """
 3     全連接層和激活層的合並
 4 
 5     Inputs:
 6     - x: 全連接層的輸入
 7     - w, b: 全連接層的權重參數
 8 
 9     Returns a tuple of:
10     - out: 被激活的全連接層的輸出
11     - cache: 用於反向傳播
12     """
13     a,fc_cache=affine_forward(x,w,b)
14     out,relu_cache=relu_forward(a)
15     return out, (fc_cache,relu_cache)
16 
17 
18 def affine_relu_backward(dout, cache):
19     fc_cache, relu_cache = cache
20     da=relu_backward(dout,relu_cache)
21     dx,dw,db=affine_backward(da,fc_cache)
22     return dx, dw, db
View Code

3、loss層

嚴格上講這不算神經網絡的一個層,只是為了訓練而必須進行的一個計算,但是這里我們就把它也當作一個層好了。

loss函數在上一個博客中已經詳細介紹過了。https://www.cnblogs.com/super-JJboom/p/9748468.html

 1 def svm_loss(x, y):
 2     
 3     N,C=x.shape
 4     correct_class_scores=x[range(N),y].reshape(-1,1)
 5     margins=np.maximum(0,x-correct_class_scores+1)
 6     loss=np.sum(margins)/N
 7     dx = np.zeros_like(x)
 8     dx[margins>0]=1
 9     num_pos = np.sum(margins > 0, axis=1)
10     dx[range(N),y]-=num_pos
11     dx/=N
12     
13     return loss, dx
14 
15 
16 def softmax_loss(x, y):
17     
18     N,C=x.shape
19     shift_x=x-np.max(x,axis=1,keepdims=True).reshape(-1,1)
20     Z=np.sum(np.exp(shift_x),axis=1,keepdims=True)
21     log_pro=-shift_x+np.log(Z)
22     loss=np.sum(log_pro[range(N),y])/N
23     probs=np.exp(-log_pro)
24     probs[range(N),y]-=1
25     dx=probs
26     dx/=N
View Code

到此為止,之前我們實現兩層神經網絡需要的層已經都實現了。先重構一下之前的實現吧。

 1 #2層神經網絡
 2 class TwoLayerNet(object):
 3     #The architecure : affine - relu - affine - softmax.
 4     def __init__(self, input_dim=3*32*32,hidden_dim=100,num_classes=10,weight_scale=1e-3,reg=0.0):
 5         ################################
 6         # input_dim      輸入維度
 7         # hidden_dims    隱藏層神經元個數
 8         # num_classes    輸出個數
 9         # weight_scale   初始化權重
10         # reg            正則項系數
11         ################################
12         self.params={}
13         self.reg=reg
14         self.params['W1']=weight_scale*np.random.randn(input_dim,hidden_dim)
15         self.params['b1']=np.zeros(hidden_dim)
16         self.params['W2']=weight_scale*np.random.randn(hidden_dim,num_classes)
17         self.params['b2']=np.zeros(num_classes)
18 
19     def loss(self,X,y=None):
20         #返回loss和grad
21 
22         #前向計算
23         ar1_out,ar1_cache=affine_relu_forward(X,self.params['W1'],self.params['b1'])
24         a2_out,a2_cache=affine_forward(ar1_out,self.params['W2'],self.params['b2'])
25         scores=a2_out
26 
27         if y is None:
28             return scores
29 
30         loss,grads=0,{}
31         loss,dscores=softmax_loss(scores,y)
32         loss=loss+0.5*self.reg*(np.sum(self.params['W1']**2)+np.sum(self.params['W2']**2))
33         dx2,dw2,db2=affine_backward(dscores,a2_cache)
34         grads['W2']=dw2+self.reg*self.params['W2']
35         grads['b2']=db2
36 
37         dx1,dw1,db1=affine_relu_backward(dx2,ar1_cache)
38         grads['W1']=dw1+self.reg*self.params['W1']
39         grads['b1']=db1
40 
41         return loss,grads
View Code

看起來比之前的實現並沒有簡單多少。。。這是因為2層神經網絡的結構過於簡單,僅從代碼量上來看並沒有減少,但是對於后面要實現的更復雜的神經網絡來說,就發揮了巨大的作用。

哦,對比之前的實現,發現少了自動化訓練的實現。因為訓練有很多參數可以選擇和調節,之前沒有實現,如果全部放入神經網絡的類中的話會顯得過於臃腫,所以把訓練過程的實現單獨拿出來作為一個類。

4、自動化訓練

相比與之前的自動訓練過程,這里增加了更多的可選項。可以選擇優化方法,如:SGD,帶動量的SGD,adam。每一輪數據迭代完之后顯示數據。

  1 import numpy as np 
  2 from cs231n import optim
  3 
  4 class Solver(object):
  5     def __init__(self,model,data,**kwargs):
  6         '''
  7         初始化對象
  8         inputs:
  9         - model:網絡結構對象
 10         - data:字典,包含帶標簽的訓練集和驗證集
 11         - kwargs:可選參數,詳細見下面提取時候的注釋
 12         '''
 13 
 14         self.model=model
 15         self.X_train=data['X_train']
 16         self.y_train=data['y_train']
 17         self.X_val=data['X_val']
 18         self.y_val=data['y_val']
 19 
 20         #解讀kwargs
 21         self.update_rule=kwargs.pop('update_rule','sgd') #優化方法的選擇,默認為隨機梯度下降
 22         self.optim_config=kwargs.pop('optim_config',{})  #優化的參數,學習率是必須有的選項。其他可以有動量因子之類的參數
 23         self.lr_decay=kwargs.pop('lr_decay',1.0)         #學習率衰減因子,默認不衰減
 24         self.batch_size=kwargs.pop('batch_size',128)     #批大小,默認128
 25         self.num_epochs=kwargs.pop('num_epochs',10)      #訓練全部數據的輪次,默認為10輪
 26         self.print_every=kwargs.pop('print_every',10)    #多少輪顯示一次進度
 27         self.verbose=kwargs.pop('verbose',True)          #是否顯示進度,為false的情況下上一個參數無效
 28 
 29         #含有不支持的參數
 30         if len(kwargs)>0:
 31             extra=','.join('"%s"' % k for k in kwargs.keys())
 32             raise ValueError('Unrecongnized arguments %s' %extra)
 33 
 34         #檢查優化方法是否支持
 35         if not hasattr(optim,self.update_rule):
 36             raise ValueError('invalid update_rule "%s"' %self.update_rule)
 37 
 38         self.update_rule=getattr(optim,self.update_rule)
 39         
 40         self._reset() 
 41 
 42     def _reset(self):
 43         #初始化參數
 44         self.epoch=0
 45         self.best_val_acc=0
 46         self.best_params={}
 47         self.loss_history=[]
 48         self.train_acc_history=[]
 49         self.val_acc_history=[]
 50 
 51         #給給個參數矩陣復制一個優化參數,因為之后每個權重的參數不相同,需要自己保存
 52         self.optim_configs={}
 53         for p in self.model.params:
 54             d={k:v for k,v in self.optim_config.items()}
 55             self.optim_configs[p]=d
 56 
 57     def _step(self):
 58         #單步更新
 59 
 60         #隨機取出batchsize個數據
 61         num_train=self.X_train.shape[0]
 62         batch_mask=np.random.choice(num_train,self.batch_size)
 63         X_batch=self.X_train[batch_mask]
 64         y_batch=self.y_train[batch_mask]
 65 
 66         #計算loss
 67         loss,grads=self.model.loss(X_batch,y_batch)
 68         self.loss_history.append(loss)
 69 
 70         #更新參數
 71         for p,w in self.model.params.items():
 72             dw=grads[p]
 73             config=self.optim_configs[p]
 74             next_w,next_config=self.update_rule(w,dw,config)
 75             self.model.params[p]=next_w
 76             self.optim_configs[p]=next_config
 77 
 78     #計算正確率
 79     def check_accuracy(self,X,y,num_samples=None,batch_size=128):
 80             N=X.shape[0]
 81 
 82             #如果num_sample不為空 則只從全部數據中選則num_sample個數據計算
 83             if num_samples is not None and N>num_samples:
 84                 mask=np.random.choice(N,num_samples)
 85                 N=num_samples
 86                 X=X[mask]
 87                 y=y[mask]
 88 
 89             num_batches=N//batch_size
 90             if N%batch_size!=0:
 91                 num_batches+=1
 92             y_pred=[]
 93             for i in range(num_batches):
 94                 start=i*batch_size
 95                 end=(i+1)*batch_size
 96                 scores=self.model.loss(X[start:end])
 97                 y_pred.append(np.argmax(scores,axis=1))
 98             y_pred=np.concatenate(y_pred,axis=0)
 99             acc=np.mean(y_pred==y)
100 
101             return acc
102 
103     def train(self):
104         num_train=self.X_train.shape[0]
105         iterations_per_epoch=max(num_train//self.batch_size,1)
106         num_iterations=self.num_epochs*iterations_per_epoch
107 
108         for t in range(num_iterations):
109             self._step()
110 
111             if self.verbose and t%self.print_every==0:
112                 print('Iteration %d /%d loss: %f' %(t+1,num_iterations,self.loss_history[-1]) )
113 
114             #每個epoch執行相應操作
115             epoch_end=(t+1)%iterations_per_epoch==0
116             if epoch_end:
117                 self.epoch+=1
118                 for k in self.optim_configs:
119                     self.optim_configs[k]['learning_rate']*=self.lr_decay
120 
121             first_it=(t==0)
122             last_it=(t==num_iterations-1)
123             if first_it or last_it or epoch_end:
124                 train_acc=self.check_accuracy(self.X_train,self.y_train,num_samples=1280)
125                 val_acc=  self.check_accuracy(self.X_val  ,self.y_val)
126                 self.train_acc_history.append(train_acc)
127                 self.val_acc_history.append(val_acc)
128 
129                 #可視化進度
130                 if self.verbose:
131                     print ('(Epoch %d / %d) train acc: %f; val_acc: %f' % (
132                      self.epoch, self.num_epochs, train_acc, val_acc))
133 
134                 #檢查、保存模型
135                 if val_acc>self.best_val_acc:
136                     self.best_val_acc=val_acc
137                     self.best_params={}
138                     for k,v in self.model.params.items():
139                         self.best_params[k]=v.copy()
140 
141             self.model.params=self.best_params
View Code

實現到這里,已經可以重新訓練之前的兩層神經網絡了,訓練代碼全部整合帶最后的測試代碼里面了。

5、實現全連層神經網絡框架

實現跟兩層神經網絡區別不大,只是網絡層的堆疊使用了循環。這里還沒有實現的批歸一化和dropout操作后面會講到。

  1 class FullyConnectedNet(object):
  2     #archtecture: {affine - [batch norm] - relu - [dropout]} x (L - 1) - affine - softmax
  3     def __init__(self,hidden_dims,input_dim=3*32*32,num_classes=10,dropout=0,
  4                  use_batchnorm=False,reg=0.0,weight_scale=1e-3,dtype=np.float32,seed=None):
  5 
  6         '''
  7         inputs:
  8         - hidden_dims:list,存儲了有多少個中間層,每一層有多少個神經元
  9         - input_dim: 輸入數據的維度大小
 10         - num_classes:類別的個數,也就是最后一層的神經元個數
 11         - dropout:失活率
 12         - use_batchnorm:是否在每一層之間使用批歸一化操作
 13         - reg:正則權重
 14         - weight_scale:權重矩陣的初始數量級
 15         - seed:失活率隨機
 16         '''
 17 
 18         self.use_batchnorm=use_batchnorm
 19         self.use_dropout=(dropout>0)
 20         self.reg=reg
 21         self.num_layers=1+len(hidden_dims)
 22         self.dtype=dtype
 23         self.params={}
 24 
 25         #初始化每層的參數w,b [gamma,beta,dropout](如果有的話)
 26         layer_input=input_dim
 27         for i,hd in enumerate(hidden_dims):
 28             self.params['W%d'%(i+1)]=weight_scale*np.random.randn(layer_input,hd)
 29             self.params['b%d'%(i+1)]=weight_scale*np.zeros(hd)
 30             if self.use_batchnorm:
 31                 self.params['gamma%d'%(i+1)]=np.ones(hd)
 32                 self.params['beta%d'%(i+1)]=np.zeros(hd)
 33             layer_input=hd
 34         self.params['W%d'%(self.num_layers)]=weight_scale*np.random.randn(layer_input,num_classes)
 35         self.params['b%d'%(self.num_layers)]=weight_scale*np.zeros(num_classes)
 36         for k,v in self.params.items():
 37             self.params[k]=v.astype(dtype)
 38 
 39         self.dropout_param={}
 40         if self.use_dropout:
 41             self.dropout_param={'mode':'train','p':dropout}
 42             if seed is not None:
 43                 self.dropout_param['seed']=seed
 44 
 45         self.bn_params=[]
 46         if self.use_batchnorm:
 47             self.bn_params=[{'mode':'train'} for i in range(self.num_layers-1)]
 48 
 49     def loss(self,X,y=None):
 50         
 51         #跟之前一樣,y=None時表示測試過程,直接返回最后一層的輸出即可。否則表示訓練過程,還要計算loss和gradient。
 52 
 53         X=X.astype(self.dtype)
 54         mode='test' if y is None else 'train'
 55         
 56         if self.dropout_param is not None:
 57             self.dropout_param['mode'] = mode   
 58         if self.use_batchnorm:
 59             for bn_param in self.bn_params:
 60                 bn_param['mode'] = mode
 61 
 62 
 63         #forward pass
 64         layer_input=X
 65         ar_cache={}
 66         dp_cache={}
 67 
 68         for lay in range(self.num_layers-1):
 69             if self.use_batchnorm:
 70                 layer_input, ar_cache[lay] = affine_bn_relu_forward(layer_input, 
 71                                         self.params['W%d'%(lay+1)], self.params['b%d'%(lay+1)], 
 72                                         self.params['gamma%d'%(lay+1)], self.params['beta%d'%(lay+1)], self.bn_params[lay])
 73             else:
 74                 layer_input,ar_cache[lay]=affine_relu_forward(layer_input,self.params['W%d'%(lay+1)],self.params['b%d'%(lay+1)])
 75 
 76             if self.use_dropout:
 77                 layer_input,  dp_cache[lay] = dropout_forward(layer_input, self.dropout_param)
 78 
 79         ar_out,ar_cache[self.num_layers]=affine_forward(layer_input,self.params['W%d'%(self.num_layers)],self.params['b%d'%(self.num_layers)])
 80         scores=ar_out
 81 
 82         #預測時直接返回scores即可
 83         if mode=='test':
 84             return scores
 85 
 86         #訓練時還要計算loss和gradient
 87         grads={}
 88         loss,dscores=softmax_loss(scores,y)
 89         dhout=dscores
 90         loss+=0.5*self.reg*np.sum(self.params['W%d'%(self.num_layers)]**2)
 91         dx,dw,db=affine_backward(dhout,ar_cache[self.num_layers])
 92         grads['W%d'%(self.num_layers)]=dw+self.reg*self.params['W%d'%(self.num_layers)]
 93         grads['b%d'%(self.num_layers)]=db
 94         dhout=dx
 95         for lay in range(self.num_layers-1):
 96             lay=self.num_layers-1-lay-1
 97             loss+=0.5*self.reg*np.sum(self.params['W%d'%(lay+1)]**2)
 98             if self.use_dropout:
 99                 dout=dropout_backward(dhout,dp_cache[lay])
100             if self.use_batchnorm:
101                 dx,dw,db,dgamma,dbeta=affine_bn_relu_backward(dhout,ar_cache[lay])
102                 grads['gamma%d'%(lay+1)] = dgamma
103                 grads['beta%d'%(lay+1)] = dbeta
104             else:
105                 dx,dw,db=affine_relu_backward(dhout,ar_cache[lay])
106             grads['W%d'%(lay+1)]=dw+self.reg*self.params['W%d'%(lay+1)]
107             grads['b%d'%(lay+1)]=db
108             dhout=dx
109 
110         return loss,grads
View Code

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM