目錄
- 神經(jīng)網(wǎng)絡(luò)框架使用方法及設(shè)計(jì)思想
神經(jīng)網(wǎng)絡(luò)框架使用方法及設(shè)計(jì)思想
- 在框自己手寫架上基本模仿pytorch,用以學(xué)習(xí)神經(jīng)網(wǎng)絡(luò)的基本算法,如前向傳播、反向傳播、各種層、各種激活函數(shù)
- 采用面向?qū)ο蟮乃枷脒M(jìn)行編程,思路較為清晰
- 想要神經(jīng)網(wǎng)絡(luò)的同學(xué)們可以參考一下
- 代碼大體框架較為清晰,但不否認(rèn)存在丑陋的部分,以及對于pytorch的拙劣模仿
項(xiàng)目介紹
手寫數(shù)字識別,使用MINST數(shù)據(jù)集
訓(xùn)練30輪可以達(dá)到93%準(zhǔn)確度,訓(xùn)練500輪左右達(dá)到95%準(zhǔn)確度無法繼續(xù)上升
使用循環(huán)神經(jīng)網(wǎng)絡(luò)RNN,用sin的曲線預(yù)測cos的曲線
目前仍有bug,無法正常訓(xùn)練
框架介紹
- 與框架有關(guān)的代碼都放在了mtorch文件夾中
- 使用流程
與pytorch相似,需要定義自己的神經(jīng)網(wǎng)絡(luò)、損失函數(shù)、梯度下降的優(yōu)化算法等等
在每一輪的訓(xùn)練中,先獲取樣本輸入將其輸入到自己的神經(jīng)網(wǎng)絡(luò)中獲取輸出。然后將預(yù)測結(jié)果和期望結(jié)果交給損失函數(shù)計(jì)算loss,并通過loss進(jìn)行梯度的計(jì)算,最后通過優(yōu)化器對神經(jīng)網(wǎng)絡(luò)的參數(shù)進(jìn)行更新。
結(jié)合代碼理解更佳👇:
以下是使用MINST數(shù)據(jù)集的手寫數(shù)字識別的主體代碼
# 定義網(wǎng)絡(luò) define neural network
class DigitModule(Module):
def __init__(self):
# 計(jì)算順序就會按照這里定義的順序進(jìn)行
sequential = Sequential([
layers.Linear2(in_dim=ROW_NUM * COLUM_NUM, out_dim=16, coe=2),
layers.Relu(16),
layers.Linear2(in_dim=16, out_dim=16, coe=2),
layers.Relu(16),
layers.Linear2(in_dim=16, out_dim=CLASS_NUM, coe=1),
layers.Sigmoid(CLASS_NUM)
])
super(DigitModule, self).__init__(sequential)
module = DigitModule() # 創(chuàng)建模型 create module
loss_func = SquareLoss(backward_func=module.backward) # 定義損失函數(shù) define loss function
optimizer = SGD(module, lr=learning_rate) # 定義優(yōu)化器 define optimizer
for i in range(EPOCH_NUM): # 共訓(xùn)練EPOCH_NUM輪
trainning_loss = 0 # 計(jì)算一下當(dāng)前一輪訓(xùn)練的loss值,可以沒有
for data in train_loader: # 遍歷所有樣本,train_loader是可迭代對象,保存了數(shù)據(jù)集中所有的數(shù)據(jù)
imgs, targets = data # 將數(shù)據(jù)拆分成圖片和標(biāo)簽
outputs = module(imgs) # 將樣本的輸入值輸入到自己的神經(jīng)網(wǎng)絡(luò)中
loss = loss_func(outputs, targets, transform=True) # 計(jì)算loss / calculate loss
trainning_loss += loss.value
loss.backward() # 通過反向傳播計(jì)算梯度 / calculate gradiant through back propagation
optimizer.step() # 通過優(yōu)化器調(diào)整模型參數(shù) / adjust the weights of network through optimizer
if i % TEST_STEP == 0: # 每訓(xùn)練TEST_STEP輪就測試一下當(dāng)前訓(xùn)練的成果
show_effect(i, module, loss_func, test_loader, i // TEST_STEP)
print("{} turn finished, loss of train set = {}".format(i, trainning_loss))
接下來逐個介紹編寫的類,這些類在pytorch中都有同名同功能的類,是仿照pytorch來的:
與pytorch不同,只能有一個Sequential類(序列),在該類中定義好神經(jīng)網(wǎng)絡(luò)的各個層和順序,然后傳給Module類的構(gòu)造函數(shù)
正向傳播:調(diào)用Sequential的正向傳播
反向傳播:調(diào)用Sequential的反向傳播
目前為止,這個類的大部分功能與Sequential相同,只是套了個殼保證與pytorch相同
有不同的loss函數(shù),構(gòu)造函數(shù)需要給他指定自己定義的神經(jīng)網(wǎng)絡(luò)的反向傳播函數(shù)
調(diào)用loss函數(shù)會返回一個Loss類的對象,該類記錄了loss值。
通過調(diào)用Loss類的.backward()方法就可以實(shí)現(xiàn)反向傳播計(jì)算梯度
內(nèi)部機(jī)制:
內(nèi)部其實(shí)就是調(diào)用了自己定義的神經(jīng)網(wǎng)絡(luò)的反向傳播函數(shù)
也算是對于pytorch的一個拙劣模仿,完全沒必要,直接通過Module調(diào)用就好
目前只實(shí)現(xiàn)了隨機(jī)梯度下降SGD
構(gòu)造函數(shù)的參數(shù)是自己定義的Module。在已經(jīng)計(jì)算過梯度之后,調(diào)用optimizer.step()改變Module內(nèi)各個層的參數(shù)值
內(nèi)部機(jī)制:
目前由于只有SGD一種算法,所以暫時也只是一個拙劣模仿
就是調(diào)用了一下Module.step(),再讓Module調(diào)用Sequential.step(),最后由Sequential調(diào)用內(nèi)部各個層的Layer.step()實(shí)現(xiàn)更新
梯度值在loss.backward的時候計(jì)算、保存在各個層中了
有許多不同的層
共性
前向傳播:
接受一個輸入進(jìn)行前向傳播計(jì)算,輸出一個輸出
會將輸入保存起來,在反向傳播中要用
反向傳播:
接受前向傳播的輸出的梯度值,計(jì)算自身參數(shù)(如Linear中的w和b)的梯度值并保存起來
輸出值為前向傳播的輸入的梯度值,用來讓上一層(可能沒有)繼續(xù)進(jìn)行反向傳播計(jì)算
這樣不同的層之間就可以進(jìn)行任意的拼裝而不妨礙前向傳播、反向傳播的進(jìn)行了
.step方法
更新自身的參數(shù)值(也可能沒有,如激活層、池化層)
這個類也是繼承自Layer,可以當(dāng)作一層來使用
它把多個層按照順序拼裝到一起,在前向、反向傳播時按照順序進(jìn)行計(jì)算
結(jié)合它的forward、backward方法來理解:
def forward(self, x):
out = x
for layer in self.layers:
out = layer(out)
return out
def backward(self, output_gradiant):
layer_num = len(self.layers)
delta = output_gradiant
for i in range(layer_num - 1, -1, -1):
# 反向遍歷各個層, 將期望改變量反向傳播
delta = self.layers[i].backward(delta)
def step(self, lr):
for layer in self.layers:
layer.step(lr)
- RNN類:循環(huán)神經(jīng)網(wǎng)絡(luò)層
繼承自Layer,由于內(nèi)容比較復(fù)雜故單獨(dú)說明一下
RNN內(nèi)部由一個全連接層Linear和一個激活層組成
前向傳播
def forward(self, inputs):
"""
:param inputs: input = (h0, x) h0.shape == (batch, out_dim) x.shape == (seq, batch, in_dim)
:return: outputs: outputs.shape == (seq, batch, out_dim)
"""
h = inputs[0] # 輸入的inputs由兩部分組成
X = inputs[1]
if X.shape[2] != self.in_dim or h.shape[1] != self.out_dim:
# 檢查輸入的形狀是否有問題
raise ShapeNotMatchException(self, "forward: wrong shape: h0 = {}, X = {}".format(h.shape, X.shape))
self.seq_len = X.shape[0] # 時間序列的長度
self.inputs = X # 保存輸入,之后的反向傳播還要用
output_list = [] # 保存每個時間點(diǎn)的輸出
for x in X:
# 按時間序列遍歷input
# x.shape == (batch, in_dim), h.shape == (batch, out_dim)
h = self.activation(self.linear(np.c_[h, x]))
output_list.append(h)
self.outputs = np.stack(output_list, axis=0) # 將列表轉(zhuǎn)換成一個矩陣保存起來
return self.outputs
反向傳播
def backward(self, output_gradiant):
"""
:param output_gradiant: shape == (seq, batch, out_dim)
:return: input_gradiant
"""
if output_gradiant.shape != self.outputs.shape:
# 期望得到(seq, batch, out_dim)形狀
raise ShapeNotMatchException(self, "__backward: expected {}, but we got "
"{}".format(self.outputs.shape, output_gradiant.shape))
input_gradients = []
# 每個time_step上的虛擬weight_gradient, 最后求平均值就是總的weight_gradient
weight_gradients = np.zeros(self.linear.weights_shape())
bias_gradients = np.zeros(self.linear.bias_shape())
batch_size = output_gradiant.shape[1]
# total_gradient: 前向傳播的時候是將x, h合成為一個矩陣,所以反向傳播也先計(jì)算這個大矩陣的梯度再拆分為x_grad, h_grad
total_gradient = np.zeros((batch_size, self.out_dim + self.in_dim))
h_gradient = None
# 反向遍歷各個時間層,計(jì)算該層的梯度值
for i in range(self.seq_len - 1, -1, -1):
# 前向傳播順序: x, h -> z -> h
# 所以反向傳播計(jì)算順序:h_grad -> z_grad -> x_grad, h_grad, w_grad, b_grad
# %%%%%%%%%%%%%%計(jì)算平均值的版本%%%%%%%%%%%%%%%%%%%%%%%
# h_gradient = (output_gradiant[i] + total_gradient[:, 0:self.out_dim]) / 2
# %%%%%%%%%%%%%%不計(jì)算平均值的版本%%%%%%%%%%%%%%%%%%%%%%%
# 計(jì)算h_grad: 這一時間點(diǎn)的h_grad包括輸出的grad和之前的時間點(diǎn)計(jì)算所得grad兩部分
h_gradient = output_gradiant[i] + total_gradient[:, 0:self.out_dim]
# w_grad和b_grad是在linear.backward()內(nèi)計(jì)算的,不用手動再計(jì)算了
z_gradient = self.activation.backward(h_gradient) # 計(jì)算z_grad
total_gradient = self.linear.backward(z_gradient) # 計(jì)算x_grad和h_grad合成的大矩陣的梯度
# total_gradient 同時包含了h和x的gradient, shape == (batch, out_dim + in_dim)
x_gradient = total_gradient[:, self.out_dim:]
input_gradients.append(x_gradient)
weight_gradients += self.linear.gradients["w"]
bias_gradients += self.linear.gradients["b"]
# %%%%%%%%%%%%%%%%%%計(jì)算平均值的版本%%%%%%%%%%%%%%%%%%%%%%%
# self.linear.set_gradients(w=weight_gradients / self.seq_len, b=bias_gradients / self.seq_len)
# %%%%%%%%%%%%%%%%%%不計(jì)算平均值的版本%%%%%%%%%%%%%%%%%%%%%%%
self.linear.set_gradients(w=weight_gradients, b=bias_gradients) # 設(shè)置梯度值
list.reverse(input_gradients) # input_gradients是逆序的,最后輸出時需要reverse一下
print("sum(weight_gradients) = {}".format(np.sum(weight_gradients)))
# np.stack的作用是將列表轉(zhuǎn)變成一個矩陣
return np.stack(input_gradients), h_gradient
以上就是numpy創(chuàng)建神經(jīng)網(wǎng)絡(luò)框架的詳細(xì)內(nèi)容,更多關(guān)于numpy神經(jīng)網(wǎng)絡(luò)的資料請關(guān)注腳本之家其它相關(guān)文章!
您可能感興趣的文章:- 教你使用Python建立任意層數(shù)的深度神經(jīng)網(wǎng)絡(luò)
- python神經(jīng)網(wǎng)絡(luò)編程之手寫數(shù)字識別
- python機(jī)器學(xué)習(xí)之神經(jīng)網(wǎng)絡(luò)
- pytorch動態(tài)神經(jīng)網(wǎng)絡(luò)(擬合)實(shí)現(xiàn)
- Python如何使用神經(jīng)網(wǎng)絡(luò)進(jìn)行簡單文本分類
- pytorch之深度神經(jīng)網(wǎng)絡(luò)概念全面整理