您当前的位置: 首页 >  分类

每周一个机器学习小项目001:全链接层实现与分类问题

蔚1 发布时间:2018-07-03 02:45:59 ,浏览量:5

打算更新一个小的系列,不定期更新,但是题目定为《每周一个机器学习小项目》,主要偏重于深度学习内容,穿插一些传统的机器学习算法的理论和实现。没有什么特色,也没有什么噱头,甚至于名字都很普通。主要是想用200行左右的代码(限定为 Python,如果超过再改简介)实现一些机器学习算法。过去说完全熟悉机器学习至少需要三年的时间,所以大概会更新这么久。希望读者如果真的想从事机器学习工作,还是脚踏实地一步一步来,学习这种事快不得,学成后工程实践又是三年。门槛这种事情都是时间和精力堆起来的,所有速成都会增加以后学习的成本。

主要几个特色:

  1. 主要使用的语言为 Python3+Numpy。
  2. 尽量给出中英文对照,每篇文章都会推荐相关文献;
  3. 描述理论过程中尽量不使用图片、比喻;
  4. 后期有时间会更新GPU实现相关文章,这肯定不止200行;
  5. 不会使用 TensorFlow、Caffe 以及其他任何机器学习库,cuDNN 除外。
1. 阅读建议

推荐阅读时间:20min

推荐阅读文章:Rumelhart D E, Hinton G E, Williams R J. Learning representations by back-propagating errors.[J]. 1986, 323(6088):399-421.

2. 软件环境
  • Python3
  • Numpy
3. 前置基础
  • 导数
  • 矩阵运算
4. 数据描述
  • 数据来源:kaggle
  • 数据下载:kanggle 竞赛页面
  • 数据描述:欧洲的信用卡持卡人在 2013 年 9 月 2 天时间里的 284807 笔交易数据,其中有 492 笔交易是欺诈交易,占比 0.172%。数据采用 PCA 变换映射为 V1、V2、…、V28 数值型属性,只有交易时间和金额这两个变量没有经过 PCA 变换。输出变量为二值变量,1 为正常,0 为欺诈交易。
5. 理论部分 5.1 全链接层

全链接输入与输出矩阵格式为 [BATCHSIZE, Features], 假设网络某一层输入为 $x^l$,输出为 $x^{l+1}$,那么全链接网络输入与输出层间关系为:enter image description here

5.2 链式求导

链式求导是目前为止整个深度学习的基础,有人将链式求导法则称之为反向传播。反向传播是从 $loss$ 函数开始的。

传播过程之中每一层均会计算两个内容:

  1. 本层可训练参数的导数,
  2. 本层向前传播误差(链式求导)。

举个例子来说:

enter image description here

这里定义了几个计算:enter image description here

$1.3$ 中将 $1.2$ 中每个操作均算为一个计算单元,对于 $g=a\cdot f_2$ 这一层来说,有一个可训练参数 a,那么反向传播需要计算可训练参数 a 的导数 $1.1-(e)$,同时为了计算 $f_3$ 之中的可训练参数,此层需要产生新的 $e_3$。对于 $g=f_1(\cdot)$ 这一层来说,由于没有可训练参数,因此仅产生反向传播误差 $e_2$。

再次强调 $1.2$ 中将每一步计算,包括相乘、相加、通过函数均算为单独的计算层。因此全链接层包括:矩阵相乘(wx)-矩阵相加(wx+b)-函数计算(f(wx+b))三个计算层。

5.3 矩阵相乘正向计算

矩阵正向计算过程中比较简单,仅是一个矩阵相乘:$$x^{l+1}=W^l\cdot x^l$$(1.4)

$1.4$ 为 $1.1-a$,这里将其独立为单独一层。上标 $l$ 代表层号。

5.4 矩阵相乘反向传播

矩阵相乘 $1.4$,假设此层反向传播误差为 $e^l$,那么误差传递函数为:

enter image description here

此层可训练参数的导数为:

enter image description here

5.5 偏置项正向计算

偏置项计算正向计算方式为 $1.1-b$,所描述的过程:$$x^{l+1}= x^l+b^l$$(1.7)

5.6 偏置项导数可训练参数导数与误差传播

加入偏置项对应于 $1.7$,此步之中误差传播方式为:

$$e^{l}=\frac{\partial loss}{\partial x^l}=\frac{\partial loss}{x^{l+1}}\frac{\partial x^l}{\partial x^{l}}=e^{l+1}$$(1.8)

也就是不发生变化,而可训练参数的导数为:

$$\frac{\partial loss}{\partial b^l}=e^l$$(1.9)

5.7 激活函数层可训练参数导数与误差传播

激活函数对应于 $1.1-c$,此步之中无可训练参数,因此仅需计算误差传播项:

$$e^{l+1}=\frac{\partial loss}{\partial x^l}=\frac{\partial loss}{\partial x^{l+1}}\frac{\partial (x^{l+1})}{\partial x^l}=e^lf'(u)$$(1.10)

6. 代码部分 6.1 结构分析

可以看到将神经网络拆分成几个计算层后,每一层都有两个导数需要计算:反向传播误差与可训练参数的导数。每一层又分为两个部分,第一个部分用于计算正向传播过程,第二个部分用于计算反向传播过程。导数与误差均在反向传播过程之中计算,全链接层包括四个组件:矩阵相乘、偏置相加、激活函数、损失函数:

6.2 矩阵相乘
    def _matmul(self, inputs, W, *args, **kw):        """        正向传播        """        return np.dot(inputs, W)    def _d_matmul(self, in_error, n_layer, layer_par):        """        反向传播        """        W = self.value[n_layer]        inputs = self.outputs[n_layer]        self.d_value[n_layer] = np.dot(inputs.T, in_error)        error = np.dot(in_error, W.T)        return error    def matmul(self, filters, *args, **kw):        self.value.append(filters)        self.d_value.append(np.zeros_like(filters))        self.layer.append((self._matmul, None, self._d_matmul, None))        self.layer_name.append("matmul")
6.3 加入偏置
    def _bias_add(self, inputs, b, *args, **kw):        return inputs + b    def _d_bias_add(self, in_error, n_layer, layer_par):        self.d_value[n_layer] = np.sum(in_error, axis=0)        return in_error    def bias_add(self, bias, *args, **kw):        self.value.append(bias)        self.d_value.append(np.zeros_like(bias))        self.layer.append((self._bias_add, None, self._d_bias_add, None))        self.layer_name.append("bias_add")
6.4 通过激活函数
    def _sigmoid(self, X, *args, **kw):        return 1/(1+np.exp(-X))    def _d_sigmoid(self, in_error, n_layer, *args, **kw):        X = self.outputs[n_layer]        return in_error * np.exp(-X)/(1 + np.exp(-X)) ** 2    def sigmoid(self):        self.value.append([])        self.d_value.append([])        self.layer.append((self._sigmoid, None, self._d_sigmoid, None))        self.layer_name.append("sigmoid")        
6.5 loss 函数

为了简便,使用二范数作为 $loss$ 函数:

    def _loss_square(self, Y, *args, **kw):        B = np.shape(Y)[0]        return np.square(self.outputs[-1] - Y)/B    def _d_loss_square(self, Y, *args, **kw):        B = np.shape(Y)[0]        return 2 * (self.outputs[-2] - Y)    def loss_square(self):        self.value.append([])        self.d_value.append([])        self.layer.append((self._loss_square, None, self._d_loss_square, None))            self.layer_name.append("loss")
6.6 代码集成

将上面所叙述的代码进行集成,集成过程需要用到的函数为正向、反向传播代码:

    def forward(self, X):        self.outputs = []        self.outputs.append(X)        net = X        for idx, lay in enumerate(self.layer):            method, layer_par, _, _ = lay            net = method(net, self.value[idx], layer_par)            self.outputs.append(net)        return    def backward(self, Y):        error = self.layer[-1][2](Y)        self.n_layer = len(self.value)        for itr in range(self.n_layer-2, -1, -1):            _, _, method, layer_par = self.layer[itr]            error = method(error, itr, layer_par)

训练过程需要不断的正向传播-反向传播循环,并将所计算的可训练参数的偏导数加入原有变量之中,这称之为随机梯度下降法,整个执行为:

$$w^{new}\leftarrow w^{old}-\eta \cdot dw$$(1.11)

$1.11$ 为梯度下降法的标准迭代过程,$dw$ 为我们所计算的所有可训练参数的导数。

对应代码为:

    def apply_gradient(self, eta):        for idx, itr in enumerate(self.d_value):            if len(itr) == 0: continue            self.value[idx] -= itr * eta    def fit(self, X, Y):        self.forward(X)        self.backward(Y)        self.apply_gradient(0.1)    def predict(self, X):        self.forward(X)        return self.outputs[-2]

将所有函数作为一个类,类变量里包含可训练参数 $vlaue$ 以及可训练参数偏导数 $d_value$。

class NN():    def __init__(self):        # 所有可训练参数        self.value = []        # 可训练参数的导数        self.d_value = []        # 每一层输出        self.outputs = []        # 每一层所用函数        self.layer = []        # 层名        self.layer_name = []
7. 程序运行

程序运行过程,需要网络进行描述:

# 初始化值iw1 = np.random.normal(0, 0.1, [28, 28])ib1 = np.zeros([28])iw2 = np.random.normal(0, 0.1, [28, 28])ib2 = np.zeros([28])iw3 = np.random.normal(0, 0.1, [28, 2])ib3 = np.zeros([2])# 神经网络描述mtd = NN()mtd.matmul(iw1)mtd.bias_add(ib1)mtd.sigmoid()mtd.matmul(iw2)mtd.bias_add(ib2)mtd.sigmoid()mtd.matmul(iw3)mtd.bias_add(ib3)mtd.sigmoid()mtd.loss_square()# 训练for itr in range(100):    ...    mtd.fit(inx, iny)
运行结果
输出模型:Layer 0: matmulLayer 1: bias_addLayer 2: sigmoidLayer 3: matmulLayer 4: bias_addLayer 5: sigmoidLayer 6: matmulLayer 7: bias_addLayer 8: sigmoidLayer 9: loss

迭代 100 次后精度 96%。

接下来的内容
  • 卷积神经网络以及相关组件
  • 多种迭代算法比如 Adam 实现
  • 加入其它类型 loss 函数

本文首发于GitChat,未经授权不得转载,转载需与GitChat联系。

阅读全文: http://gitbook.cn/gitchat/activity/5af557a7e210d5096e9e789f

您还可以下载 CSDN 旗下精品原创内容社区 GitChat App ,阅读更多 GitChat 专享技术内容哦。

FtooAtPSkEJwnW-9xkCLqSTRpBKX

关注
打赏
1688896170
查看更多评论

蔚1

暂无认证

  • 5浏览

    0关注

    4645博文

    0收益

  • 0浏览

    0点赞

    0打赏

    0留言

私信
关注
热门博文
立即登录/注册

微信扫码登录

0.4809s