np实现sigmoid_使用numpy实现一个深度学习框架

为了理解深度学习框架的大致机理,决定使用numpy实现一个简单的神经网络框架

深度学习框架我觉得最重要的是实现了链式求导法则,而计算图就是建立在链式求导法则之上的,目前大多数深度学习是基于反向传播思想的,如何在链式计算图中进行前向传播和反向传播是深度学习框架重点要考虑的

计算图

目前主流的深度学习框架有两种计算图实现方式:静态图:先构建好图再让数据流入,优点是结构清晰,理论上速度占优(实际不一定);缺点是debug困难,对初学者学习不友好(我认为主要是对计算图的理解不深刻导致的)。典型的框架代表:Tensorflow

动态图:程序按照编写的顺序动态执行,优点是所见所得,可以随时输出动态结果,调试方便,典型的框架代表:PyTorch

个人现在更倾向于使用tensorflow,一方面是tensorflow生态较成熟,另一方面个人感觉静态图更数学化,而动态图更工程化

不过本文实现的是一个类似pytorch风格的动态图框架

知识储备

链式求导法则

[begin{align*}

& f{'} = f(x) \\

& g{'} = g(f{'}) \\

& y{'} = k(g{'}) \\

& loss = L(y, y{'})

end{align*}]

用链式求导法则计算可得

[begin{align*}

frac{d(loss)}{d(x)} = frac{d(f{'})}{d(x)} times frac{d(g{'})}{d(f{'})} times frac{d(y{'})}{d(g{'})} times frac{d(loss)}{y{'}}\\

tag{1}

end{align*}

]

常见的激活函数

激活函数几乎都是非线性且尽可能全局可导的,激活函数的选择是很重要的,sigmoid, tanh 由于自身函数的特点,只有很小的区间梯度变化比较明显,大部分的区间梯度变化很小很容易造成梯度消失,本人更倾向于使用relu,或者是 batch normalization和tanh搭配使用

sigmoid

[sigma (z) = frac{1}{1+e^{-z}} tag{2}]

用matplotlib画出它的图像1

2

3

4

5

6

7

8

9

10

11

12

13import numpy as np

import matplotlib.pyplot as plt

def (x):

return 1. / (1. + np.exp(-x))

x = np.arange(-10, 10, 0.2)

y = sigmoid(x)

plt.plot(x, y, label="sigmoid", color="blue")

plt.show()

tanh

[tanh(z) = frac{e^{z} - e^{-z}}{e^{z} + e^{-z}} tag{3}]

用matplotlib画出它的图像1

2

3

4

5

6

7

8

9

10

11

12

13import numpy as np

import matplotlib.pyplot as plt

def tanh(x):

return (np.exp(x) - np.exp(-x)) / (np.exp(x) + np.exp(-x))

x = np.arange(-10, 10, 0.2)

y = tanh(x)

plt.plot(x, y, label="tanh", color="blue")

plt.show()

relu

[relu(z) = max(0, z) = relu(z) = begin{cases}

0 & text{ if } x leqslant 0 \

z & text{ if } x > 0

end{cases} tag{4}]

用matplotlib画出它的图像1

2

3

4

5

6

7

8

9

10

11

12

13import numpy as np

import matplotlib.pyplot as plt

def relu(x):

return np.maximum(0, x)

x = np.arange(-10, 10, 0.2)

y = relu(x)

plt.plot(x, y, label="relu", color="blue")

plt.show()

常见的优化器

SGD、Adam等,推荐阅读本人的另一篇文章 常用的梯度下降优化算法

常见的损失函数

MSE

[L(x_{i}, y_{i}) = (x_{i} - y_{i})^{2} tag{5}]

NLL 负对数似然

[L(x, label) = -x_{label} tag{6}]

BCE

BinCrossEntropy 是二分类用的交叉熵

[L(x_{i}, y_{i}) = -w_{i}[y_{i} logx_{i} + (1-y_{i})log(1-x_{i})] tag{7}]

CrossEntropy 交叉熵

[begin{align*}

L(x, label) & = -w_{label} logfrac{e^{x_label}}{sum_{j=1}^{N} e^{x_{j}}} \\

& = w_{label} [-x_{label} + logsum_{j=1}^{N} e^{x_{j}}]

end{align*} tag{8}]

目录结构nn:核心的网络包NN: 基础网络类

Linear:全连接层

Variable:基础参数类

ReLU / Sigmoid / Tanh:激活函数

optim:优化器SGD

Adam

loss:损失函数MSE

CrossEntropy

init:初始化器Normal:高斯分布

TruncatedNormal:截断高斯分布

Uniform:均匀分布

fn.py:激活函数

pipe.py:pipeline

实现细节

NN

NN 是最基础的网络基类,所有定义的网络都要继承该类1

2

3

4

5

6

7

8

9

10

11class NN(object):

def __init__(self):

pass

def forward(self, *args):

pass

def backward(self, grad):

pass

def params(self):

pass

def __call__(self, *args):

return self.forward(*args)

Variable

用于保存可导变量,求导时会用到此类封装的参数1

2

3

4

5

6class Variable(object):

def __init__(self, wt, dw, b, db):

self.wt = wt

self.dw = dw

self.b = b

self.db = db

Linear

定义全连接层, 全连接层可以实现网络空间大小的放缩,全连接层是实现深网的一种方式(但不推荐,深网的构建可以使用残差网络或者高速网络来构建)

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36class Linear(NN):

def __init__(self,

dim_in,

dim_out,

init=None,

pretrained=None,

zero_bias=False):

super(Linear, self).__init__()

if isinstance(pretrained, tuple):

self.wt, self.b = pretrained

else:

if not isinstance(init, Init):

init = ini.Random([dim_in, dim_out])

self.wt = init()

if zero_bias:

self.b = ini.Zero([dim_out])()

else:

self.b = ini.Random([dim_out])()

self.input = None

self.output = None

self.dw = ini.Zero(self.wt.shape)()

self.db = ini.Zero([dim_out])()

self.variable = Variable(self.wt, self.dw, self.b, self.db)

def params(self):

return self.variable

def forward(self, *args):

self.input = args[0]

self.output = np.dot(self.input, self.wt) + self.b

return self.output

def backward(self, grad):

self.db = grad

self.dw += np.dot(self.input.T, grad)

grad = np.dot(grad, self.wt.T)

return grad

Sigmoid的实现

Sigmoid的前向推导很容易实现,也就是将输入放入sigmoid函数即可,难点在Sigmoid的求导上,以下是sigmoid的求导过程

[begin{align*}

f{'}(z) & = (frac{1}{1+e^{-z}}){'} \\

& = frac{e^{-z}}{(1+e^{-z})^{2}} \\

& = frac{1+e^{-z}-1}{(1+e^{-z})^{2}} \\

& = frac{1}{1+e^{-z}}(1-frac{1}{1+e^{-z}}) \\

& = f(z)(1-f(z))

end{align*} tag{9}]

知道了前向推导和反向推导的结果就可以用代码实现了1

2

3

4

5

6

7

8

9

10

11

12

13

14class Sigmoid(NN):

def __init__(self):

super(Sigmoid, self).__init__()

self.input = None

self.output = None

def forward(self, *args):

self.input = args[0]

self.output = 1.0 / (1.0 + np.exp(-self.input))

return self.output

def backward(self, grad):

grad *= self.output*(1.0-self.output)

return grad

tanh的实现

tanh的实现和sigmoid的类似,只要计算出tanh的导函数就可以很容易实现反向传播部分,tanh的求导结果是

[f(z){'} = 1-(f(z))^{2} tag{10}]1

2

3

4

5

6

7

8

9

10

11

12

13

14

15class Tanh(NN):

def __init__(self):

super(Tanh, self).__init__()

self.input = None

self.output = None

def forward(self, *args):

self.input = args[0]

self.output = ((np.exp(self.input) - np.exp(-self.input)) /

np.exp(self.input) + np.exp(-self.input))

return self.output

def backward(self, grad):

grad *= 1.0 - np.power(self.output, 2)

return grad

同理其他模块的实现也类似,主要包括前向传播和反向传播两部分

实例

以下构造了一个简单的BP网络1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18class BPNet(nn.NN):

def __init__(self,D_in, D_hidden, D_out):

super(Mnistnet,self).__init__()

self.layers = Pipe(

nn.Linear(D_in, D_hidden),

nn.ReLU(),

nn.Linear(D_hidden, D_out)

)

self.criterion = loss.MSE()

def forward(self,*args):

x = args[0]

return self.layers.forward(x)

def backward(self,grad=None):

grad=self.criterion.backward(grad)

self.layers.backward(grad)

可视化训练

使用simnet实现了一个简单的BP网络,数据集是 mnist,做了可视化训练demo , 效果如下图

refrence

你可能感兴趣的:(np实现sigmoid)