前言
在了解深度学习框架之前,我们需要自己去理解甚至去实现一个网络学习和调参的过程,进而理解深度学习的机理;
为此,博主这里提供了一个自己编写的一个例子,带领大家理解一下网络学习的正向传播和反向传播的过程;
除此之外,为了实现batch读取,我还设计并提供了一个简单的DataLoader类去模拟深度学习中数据迭代器的取样;并且提供了存取模型的函数;
值得注意的是仅仅使用python实现,因此对于环境的需求不是很大,希望各位可以多多star我的博客和github,学习到更有用的知识!!

目录
一、实现效果
二、整体代码框架
三、详细代码说明
1.数据处理
2.网络设计
3.激活函数
4.训练
四、训练演示
五、总结
一、实现效果
实现一个由多个Linear层构成的网络来拟合函数,项目地址:https://github.com/nickhuang1996/HJLNet,运行:
python demo.py
拟合函数为:
以下结果从左到右依次为(学习率为0.03,batchsize为90):
Epoch:400,1000, 2000, 10000以上




二、整体代码框架

三、详细代码说明
1.数据处理
Dataset.py
x****是0到2之间的数据,步长为0.01,因此是200个数据;
y****是目标函数,振幅为20;
length****是数据长度;
_build_items()是建立一个dict存储x和y;
_transform()是对x和y进行数据的变换;
1import numpy as np 2 3 4class Dataset: 5 def __init__(self): 6 7 self.x = np.arange(0.0, 2.0, 0.01) 8 self.y = 20 * np.sin(2 * np.pi * self.x) 9 self.length = len(list(self.x)) 10 self._build_items() 11 self._transform() 12 13 def _build_items(self): 14 self.items = [{ 15 'x': list(self.x)[i], 16 'y': list(self.y)[i] 17 }for i in range(self.length)] 18 19 def _transform(self): 20 self.x = self.x.reshape(1, self.__len__()) 21 self.y = self.y.reshape(1, self.__len__()) 22 23 def __len__(self): 24 return self.length 25 26 def __getitem__(self, index): 27 return self.items[index]
DataLoader.py
类似于Pytorch里的DataLoader,博主这里初始化也传入两个参数:dataset和batch_size
__next__()就是每次迭代执行的函数,利用__len__()得到dataset的长度,利用__getitem__()得到数据集里的数据;
_concate()就是把一个batch的数据拼接起来;
_transform()就是转换一个batch的数据形式;
1import numpy as np 2 3 4class DataLoader: 5 def __init__(self, dataset, batch_size): 6 self.dataset = dataset 7 self.batch_size = batch_size 8 self.current = 0 9 10 def __next__(self): 11 if self.current < self.dataset.__len__(): 12 if self.current + self.batch_size <= self.dataset.__len__(): 13 item = self._concate([self.dataset.__getitem__(index) for index in range(self.current, self.current + self.batch_size)]) 14 self.current += self.batch_size 15 else: 16 item = self._concate([self.dataset.__getitem__(index) for index in range(self.current, self.dataset.__len__())]) 17 self.current = self.dataset.__len__() 18 return item 19 else: 20 self.current = 0 21 raise StopIteration 22 23 def _concate(self, dataset_items): 24 concated_item = {} 25 for item in dataset_items: 26 for k, v in item.items(): 27 if k not in concated_item: 28 concated_item[k] = [v] 29 else: 30 concated_item[k].append(v) 31 concated_item = self._transform(concated_item) 32 return concated_item 33 34 def _transform(self, concated_item): 35 for k, v in concated_item.items(): 36 concated_item[k] = np.array(v).reshape(1, len(v)) 37 return concated_item 38 39 def __iter__(self): 40 return self
2.网络设计
Linear.py
类似于Pytorch里的Linear,博主这里初始化也传入三个参数:in_features, out_features, bias
_init_parameters()是初始化权重weight和偏置bias**,weight大小是****[out_features, in_features],bias大小是[out_features, 1]**
forward就是前向传播:
1import numpy as np 2 3 4class Linear: 5 def __init__(self, in_features, out_features, bias=False): 6 self.in_features = in_features 7 self.out_features = out_features 8 self.bias = bias 9 self._init_parameters() 10 11 def _init_parameters(self): 12 self.weight = np.random.random([self.out_features, self.in_features]) 13 if self.bias: 14 self.bias = np.zeros([self.out_features, 1]) 15 else: 16 self.bias = None 17 18 def forward(self, input): 19 return self.weight.dot(input) + self.bias
*network.py
一个简单的多层Linear网络
_init_parameters()是把Linear层里的权重和偏执都放在一个dict里存储;
forward()就是前向传播,最后一层不经过Sigmoid;
backward()就是反向传播,利用梯度下降实现误差传递和调参:例如一个两层的Linear层的反向传播如下
update_grads()是更新权重和偏置;
1# -*- coding: UTF-8 -*- 2import numpy as np 3from ..lib.Activation.Sigmoid import sigmoid_derivative, sigmoid 4from ..lib.Module.Linear import Linear 5 6class network: 7 def __init__(self, layers_dim): 8 self.layers_dim = layers_dim 9 self.linear_list = [Linear(layers_dim[i - 1], layers_dim[i], bias=True) for i in range(1, len(layers_dim))] 10 self.parameters = {} 11 self._init_parameters() 12 13 def _init_parameters(self): 14 for i in range(len(self.layers_dim) - 1): 15 self.parameters["w" + str(i)] = self.linear_list[i].weight 16 self.parameters["b" + str(i)] = self.linear_list[i].bias 17 18 def forward(self, x): 19 a = [] 20 z = [] 21 caches = {} 22 a.append(x) 23 z.append(x) 24 25 layers = len(self.parameters) // 2 26 27 for i in range(layers): 28 z_temp = self.linear_list[i].forward(a[i]) 29 self.parameters["w" + str(i)] = self.linear_list[i].weight 30 self.parameters["b" + str(i)] = self.linear_list[i].bias 31 z.append(z_temp) 32 if i == layers - 1: 33 a.append(z_temp) 34 else: 35 a.append(sigmoid(z_temp)) 36 caches["z"] = z 37 caches["a"] = a 38 return caches, a[layers] 39 40 def backward(self, caches, output, y): 41 layers = len(self.parameters) // 2 42 grads = {} 43 m = y.shape[1] 44 45 for i in reversed(range(layers)): 46 # 假设最后一层不经历激活函数 47 # 就是按照上面的图片中的公式写的 48 if i == layers - 1: 49 grads["dz" + str(i)] = output - y 50 else: # 前面全部都是sigmoid激活 51 grads["dz" + str(i)] = self.parameters["w" + str(i + 1)].T.dot( 52 grads["dz" + str(i + 1)]) * sigmoid_derivative( 53 caches["z"][i + 1]) 54 grads["dw" + str(i)] = grads["dz" + str(i)].dot(caches["a"][i].T) / m 55 grads["db" + str(i)] = np.sum(grads["dz" + str(i)], axis=1, keepdims=True) / m 56 return grads 57 58 # 就是把其所有的权重以及偏执都更新一下 59 def update_grads(self, grads, learning_rate): 60 layers = len(self.parameters) // 2 61 for i in range(layers): 62 self.parameters["w" + str(i)] -= learning_rate * grads["dw" + str(i)] 63 self.parameters["b" + str(i)] -= learning_rate * grads["db" + str(i)]
3.激活函数
Sigmoid.py
公式定义:****
导数可由自身表示:
1import numpy as np 2 3 4def sigmoid(x): 5 return 1.0 / (1.0 + np.exp(-x)) 6 7 8def sigmoid_derivative(x): 9 return sigmoid(x) * (1 - sigmoid(x))
4.训练
demo.py
训练模型的入口文件,包含训练、测试和****存储模型
1from code.scripts.trainer import Trainer 2from code.config.default_config import _C 3 4 5if __name__ == '__main__': 6 trainer = Trainer(cfg=_C) 7 trainer.train() 8 trainer.test() 9 trainer.save_models()
default_config.py
配置文件**:**
layers_dim****代表Linear层的输入输出维度;
batch_size****是batch的大小;
total_epochs****是总体的训练时间,训练一次x为一个epoch;
resume****是判断继续训练;
result_img_path****是结果存储的路径;
ckpt_path****是模型存储的路径;
1from easydict import EasyDict 2 3 4_C = EasyDict() 5_C.layers_dim = [1, 25, 1] # [1, 30, 10, 1] 6_C.batch_size = 90 7_C.total_epochs = 40000 8_C.resume = True # False means retraining 9_C.result_img_path = "D:/project/Pycharm/HJLNet/result.png" 10_C.ckpt_path = 'D:/project/Pycharm/HJLNet/ckpt.npy'
trainer.py
**这里不多赘述,主要利用****train()****这个函数进行训练,****test()**进行测试
1from ..lib.Data.DataLoader import DataLoader 2from ..scripts.Dataset import Dataset 3from ..scripts.network import network 4import matplotlib.pyplot as plt 5import numpy as np 6 7 8class Trainer: 9 def __init__(self, cfg): 10 self.ckpt_path = cfg.ckpt_path 11 self.result_img_path = cfg.result_img_path 12 self.layers_dim = cfg.layers_dim 13 self.net = network(self.layers_dim) 14 if cfg.resume: 15 self.load_models() 16 self.dataset = Dataset() 17 self.dataloader = DataLoader(dataset=self.dataset, batch_size=cfg.batch_size) 18 self.total_epochs = cfg.total_epochs 19 self.iterations = 0 20 self.x = self.dataset.x 21 self.y = self.dataset.y 22 self.draw_data(self.x, self.y) 23 24 def train(self): 25 for i in range(self.total_epochs): 26 27 for item in self.dataloader: 28 caches, output = self.net.forward(item['x']) 29 grads = self.net.backward(caches, output, item['y']) 30 self.net.update_grads(grads, learning_rate=0.03) 31 if i % 100 == 0: 32 print("Epoch: {}/{} Iteration: {} Loss: {}".format(i + 1, 33 self.total_epochs, 34 self.iterations, 35 self.compute_loss(output, item['y']))) 36 self.iterations += 1 37 38 def test(self): 39 caches, output = self.net.forward(self.x) 40 self.draw_data(self.x, output) 41 self.save_results() 42 self.show() 43 44 def save_models(self): 45 ckpt = { 46 "layers_dim": self.net.layers_dim, 47 "parameters": self.net.linear_list 48 } 49 np.save(self.ckpt_path, ckpt) 50 print('Save models finish!!') 51 52 def load_models(self): 53 ckpt = np.load(self.ckpt_path).item() 54 self.net.layers_dim = ckpt["layers_dim"] 55 self.net.linear_list = ckpt["parameters"] 56 print('load models finish!!') 57 58 def draw_data(self, x, y): 59 plt.scatter(x, y) 60 61 def show(self): 62 plt.show() 63 64 def save_results(self): 65 plt.savefig(fname=self.result_img_path, figsize=[10, 10]) 66 67 # 计算误差值 68 def compute_loss(self, output, y): 69 return np.mean(np.square(output - y))
四、训练演示
训练期间会输出训练的时间,迭代次数和损失变化,训练结束存储模型和结果。
1.开始训练

2.训练完毕,读取上次的模型继续训练

3.结果展示

五、总结
如此一来便知晓了一个基本网络训练过程中正向反向传播过程,之后会更新更加详细的代码和原理,帮助各位学习深度学习的知识和概念~