1. <strong id="7actg"></strong>
    2. <table id="7actg"></table>

    3. <address id="7actg"></address>
      <address id="7actg"></address>
      1. <object id="7actg"><tt id="7actg"></tt></object>

        手把手教你從零實(shí)現(xiàn)一個(gè)深度學(xué)習(xí)框架(附代碼實(shí)現(xiàn))

        共 13942字,需瀏覽 28分鐘

         ·

        2022-01-12 06:28

        本文首先從深度學(xué)習(xí)的流程開始分析,對(duì)神經(jīng)網(wǎng)絡(luò)中的關(guān)鍵組件抽象,確定基本框架;然后再對(duì)框架里各個(gè)組件進(jìn)行代碼實(shí)現(xiàn);最后基于這個(gè)框架實(shí)現(xiàn)了一個(gè) MNIST 分類的示例,并與 Tensorflow 做了簡(jiǎn)單的對(duì)比驗(yàn)證。

        當(dāng)前深度學(xué)習(xí)框架越來(lái)越成熟,對(duì)于使用者而言封裝程度越來(lái)越高,好處就是現(xiàn)在可以非常快速地將這些框架作為工具使用,用非常少的代碼就可以構(gòu)建模型進(jìn)行實(shí)驗(yàn),壞處就是可能背后地實(shí)現(xiàn)都被隱藏起來(lái)了。在這篇文章里筆者將設(shè)計(jì)和實(shí)現(xiàn)一個(gè)、輕量級(jí)的(約 200 行)、易于擴(kuò)展的深度學(xué)習(xí)框架 tinynn(基于 Python 和 Numpy 實(shí)現(xiàn)),希望對(duì)大家了解深度學(xué)習(xí)的基本組件、框架的設(shè)計(jì)和實(shí)現(xiàn)有一定的幫助。

        本文首先會(huì)從深度學(xué)習(xí)的流程開始分析,對(duì)神經(jīng)網(wǎng)絡(luò)中的關(guān)鍵組件抽象,確定基本框架;然后再對(duì)框架里各個(gè)組件進(jìn)行代碼實(shí)現(xiàn);最后基于這個(gè)框架實(shí)現(xiàn)了一個(gè) MNIST 分類的示例,并與 Tensorflow 做了簡(jiǎn)單的對(duì)比驗(yàn)證。

        目錄

        1. 組件抽象
        2. 組件實(shí)現(xiàn)
        3. 整體結(jié)構(gòu)
        4. MNIST 例子
        5. 總結(jié)
        6. 附錄
        7. 參考

        組件抽象

        首先考慮神經(jīng)網(wǎng)絡(luò)運(yùn)算的流程,神經(jīng)網(wǎng)絡(luò)運(yùn)算主要包含訓(xùn)練 training 和預(yù)測(cè) predict (或 inference) 兩個(gè)階段,訓(xùn)練的基本流程是:輸入數(shù)據(jù) -> 網(wǎng)絡(luò)層前向傳播 -> 計(jì)算損失 -> 網(wǎng)絡(luò)層反向傳播梯度 -> 更新參數(shù),預(yù)測(cè)的基本流程是 輸入數(shù)據(jù) -> 網(wǎng)絡(luò)層前向傳播 -> 輸出結(jié)果。從運(yùn)算的角度看,主要可以分為三種類型的計(jì)算:

        1. 數(shù)據(jù)在網(wǎng)絡(luò)層之間的流動(dòng):前向傳播和反向傳播可以看做是張量 Tensor(多維數(shù)組)在網(wǎng)絡(luò)層之間的流動(dòng)(前向傳播流動(dòng)的是輸入輸出,反向傳播流動(dòng)的是梯度),每個(gè)網(wǎng)絡(luò)層會(huì)進(jìn)行一定的運(yùn)算,然后將結(jié)果輸入給下一層
        2. 計(jì)算損失:銜接前向和反向傳播的中間過(guò)程,定義了模型的輸出與真實(shí)值之間的差異,用來(lái)后續(xù)提供反向傳播所需的信息
        3. 參數(shù)更新:使用計(jì)算得到的梯度對(duì)網(wǎng)絡(luò)參數(shù)進(jìn)行更新的一類計(jì)算

        基于這個(gè)三種類型,我們可以對(duì)網(wǎng)絡(luò)的基本組件做一個(gè)抽象

        • tensor 張量,這個(gè)是神經(jīng)網(wǎng)絡(luò)中數(shù)據(jù)的基本單位
        • layer 網(wǎng)絡(luò)層,負(fù)責(zé)接收上一層的輸入,進(jìn)行該層的運(yùn)算,將結(jié)果輸出給下一層,由于 tensor 的流動(dòng)有前向和反向兩個(gè)方向,因此對(duì)于每種類型網(wǎng)絡(luò)層我們都需要同時(shí)實(shí)現(xiàn) forward 和 backward 兩種運(yùn)算
        • loss 損失,在給定模型預(yù)測(cè)值與真實(shí)值之后,該組件輸出損失值以及關(guān)于最后一層的梯度(用于梯度回傳)
        • optimizer 優(yōu)化器,負(fù)責(zé)使用梯度更新模型的參數(shù)

        然后我們還需要一些組件把上面這個(gè) 4 種基本組件整合到一起,形成一個(gè) pipeline

        • net 組件負(fù)責(zé)管理 tensor 在 layers 之間的前向和反向傳播,同時(shí)能提供獲取參數(shù)、設(shè)置參數(shù)、獲取梯度的接口
        • model 組件負(fù)責(zé)整合所有組件,形成整個(gè) pipeline。即 net 組件進(jìn)行前向傳播 -> losses 組件計(jì)算損失和梯度 -> net 組件將梯度反向傳播 -> optimizer 組件將梯度更新到參數(shù)。

        基本的框架圖如下圖

        組件實(shí)現(xiàn)

        按照上面的抽象,我們可以寫出整個(gè)流程代碼如下。

        # define model
        net = Net([layer1, layer2, ...])
        model = Model(net, loss_fn, optimizer)

        # training
        pred = model.forward(train_X)
        loss, grads = model.backward(pred, train_Y)
        model.apply_grad(grads)

        # inference
        test_pred = model.forward(test_X)

        首先定義 net,net 的輸入是多個(gè)網(wǎng)絡(luò)層,然后將 net、loss、optimizer 一起傳給 model。model 實(shí)現(xiàn)了 forward、backward 和 apply_grad 三個(gè)接口分別對(duì)應(yīng)前向傳播、反向傳播和參數(shù)更新三個(gè)功能。接下來(lái)我們看這里邊各個(gè)部分分別如何實(shí)現(xiàn)。

        tensor

        tensor 張量是神經(jīng)網(wǎng)絡(luò)中基本的數(shù)據(jù)單位,我們這里直接使用 numpy.ndarray?類作為 tensor 類的實(shí)現(xiàn)

        numpy.ndarray?https://numpy.org/doc/stable/reference/generated/numpy.ndarray.html

        layer

        上面流程代碼中 model 進(jìn)行 forward 和 backward,其實(shí)底層都是網(wǎng)絡(luò)層在進(jìn)行實(shí)際運(yùn)算,因此網(wǎng)絡(luò)層需要有提供 forward 和 backward 接口進(jìn)行對(duì)應(yīng)的運(yùn)算。同時(shí)還應(yīng)該將該層的參數(shù)和梯度記錄下來(lái)。先實(shí)現(xiàn)一個(gè)基類如下

        #?layer.py
        class?Layer(object):
        ????def?__init__(self,?name):
        ????????self.name?=?name
        ????????self.params,?self.grads?=?None,?None

        ????def?forward(self,?inputs):
        ????????raise?NotImplementedError

        ????def?backward(self,?grad):
        ????????raise?NotImplementedError

        最基礎(chǔ)的一種網(wǎng)絡(luò)層是全連接網(wǎng)絡(luò)層,實(shí)現(xiàn)如下。forward 方法接收上層的輸入 inputs,實(shí)現(xiàn) 的運(yùn)算;backward 的方法接收來(lái)自上層的梯度,計(jì)算關(guān)于參數(shù) 和輸入的梯度,然后返回關(guān)于輸入的梯度。這三個(gè)梯度的推導(dǎo)可以見附錄,這里直接給出實(shí)現(xiàn)。w_init 和 b_init 分別是參數(shù) 的初始化器,這個(gè)我們?cè)诹硗獾囊粋€(gè)實(shí)現(xiàn)初始化器中文件 initializer.py 去實(shí)現(xiàn),這部分不是核心部件,所以在這里不展開介紹。

        #?layer.py
        class?Dense(Layer):
        ????def?__init__(self,?num_in,?num_out,
        ?????????????????w_init=XavierUniformInit(),
        ?????????????????b_init=ZerosInit())
        :

        ????????super().__init__("Linear")

        ????????self.params?=?{
        ????????????"w":?w_init([num_in,?num_out]),
        ????????????"b":?b_init([1,?num_out])}

        ????????self.inputs?=?None

        ????def?forward(self,?inputs):
        ????????self.inputs?=?inputs
        ????????return?inputs?@?self.params["w"]?+?self.params["b"]

        ????def?backward(self,?grad):
        ????????self.grads["w"]?=?self.inputs.T?@?grad
        ????????self.grads["b"]?=?np.sum(grad,?axis=0)
        ????????return?grad?@?self.params["w"].T

        同時(shí)神經(jīng)網(wǎng)絡(luò)中的另一個(gè)重要的部分是激活函數(shù)。激活函數(shù)可以看做是一種網(wǎng)絡(luò)層,同樣需要實(shí)現(xiàn) forward 和 backward 方法。我們通過(guò)繼承 Layer 類實(shí)現(xiàn)激活函數(shù)類,這里實(shí)現(xiàn)了最常用的 ReLU 激活函數(shù)。func 和 derivation_func 方法分別實(shí)現(xiàn)對(duì)應(yīng)激活函數(shù)的正向計(jì)算和梯度計(jì)算。

        #?layer.py
        class?Activation(Layer):
        ????"""Base?activation?layer"""
        ????def?__init__(self,?name):
        ????????super().__init__(name)
        ????????self.inputs?=?None

        ????def?forward(self,?inputs):
        ????????self.inputs?=?inputs
        ????????return?self.func(inputs)

        ????def?backward(self,?grad):
        ????????return?self.derivative_func(self.inputs)?*?grad

        ????def?func(self,?x):
        ????????raise?NotImplementedError

        ????def?derivative_func(self,?x):
        ????????raise?NotImplementedError


        class?ReLU(Activation):
        ????"""ReLU?activation?function"""
        ????def?__init__(self):
        ????????super().__init__("ReLU")

        ????def?func(self,?x):
        ????????return?np.maximum(x,?0.0)

        ????def?derivative_func(self,?x):
        ????????return?x?>?0.0

        net

        上文提到 net 類負(fù)責(zé)管理 tensor 在 layers 之間的前向和反向傳播。forward 方法很簡(jiǎn)單,按順序遍歷所有層,每層計(jì)算的輸出作為下一層的輸入;backward 則逆序遍歷所有層,將每層的梯度作為下一層的輸入。這里我們還將每個(gè)網(wǎng)絡(luò)層參數(shù)的梯度保存下來(lái)返回,后面參數(shù)更新需要用到。另外 net 類還實(shí)現(xiàn)了獲取參數(shù)、設(shè)置參數(shù)、獲取梯度的接口,也是后面參數(shù)更新時(shí)需要用到

        #?net.py
        class?Net(object):
        ????def?__init__(self,?layers):
        ????????self.layers?=?layers

        ????def?forward(self,?inputs):
        ????????for?layer?in?self.layers:
        ????????????inputs?=?layer.forward(inputs)
        ????????return?inputs

        ????def?backward(self,?grad):
        ????????all_grads?=?[]
        ????????for?layer?in?reversed(self.layers):
        ????????????grad?=?layer.backward(grad)
        ????????????all_grads.append(layer.grads)
        ????????return?all_grads[::-1]

        ????def?get_params_and_grads(self):
        ????????for?layer?in?self.layers:
        ????????????yield?layer.params,?layer.grads

        ????def?get_parameters(self):
        ????????return?[layer.params?for?layer?in?self.layers]

        ????def?set_parameters(self,?params):
        ????????for?i,?layer?in?enumerate(self.layers):
        ????????????for?key?in?layer.params.keys():
        ????????????????layer.params[key]?=?params[i][key]

        losses

        上文我們提到 losses 組件需要做兩件事情,給定了預(yù)測(cè)值和真實(shí)值,需要計(jì)算損失值和關(guān)于預(yù)測(cè)值的梯度。我們分別實(shí)現(xiàn)為 loss 和 grad 兩個(gè)方法,這里我們實(shí)現(xiàn)多分類回歸常用的 SoftmaxCrossEntropyLoss 損失。這個(gè)的損失 loss 和梯度 grad 的計(jì)算公式推導(dǎo)進(jìn)文末附錄,這里直接給出結(jié)果:多分類 softmax 交叉熵的損失為

        梯度稍微復(fù)雜一點(diǎn),目標(biāo)類別和非目標(biāo)類別的計(jì)算公式不同。對(duì)于目標(biāo)類別維度,其梯度為對(duì)應(yīng)維度模型輸出概率減一,對(duì)于非目標(biāo)類別維度,其梯度為對(duì)應(yīng)維度輸出概率本身。

        代碼實(shí)現(xiàn)如下

        #?loss.py
        class?BaseLoss(object):
        ????def?loss(self,?predicted,?actual):
        ????????raise?NotImplementedError

        ????def?grad(self,?predicted,?actual):
        ????????raise?NotImplementedError


        class?CrossEntropyLoss(BaseLoss):
        ????def?loss(self,?predicted,?actual):
        ????????m?=?predicted.shape[0]
        ????????exps?=?np.exp(predicted?-?np.max(predicted,?axis=1,?keepdims=True))
        ????????p?=?exps?/?np.sum(exps,?axis=1,?keepdims=True)
        ????????nll?=?-np.log(np.sum(p?*?actual,?axis=1))
        ????????return?np.sum(nll)?/?m

        ????def?grad(self,?predicted,?actual):
        ????????m?=?predicted.shape[0]
        ????????grad?=?np.copy(predicted)
        ????????grad?-=?actual
        ????????return?grad?/?m

        optimizer

        optimizer 主要實(shí)現(xiàn)一個(gè)接口 compute_step,這個(gè)方法根據(jù)當(dāng)前的梯度,計(jì)算返回實(shí)際優(yōu)化時(shí)每個(gè)參數(shù)改變的步長(zhǎng)。我們?cè)谶@里實(shí)現(xiàn)常用的 Adam 優(yōu)化器。

        #?optimizer.py
        class?BaseOptimizer(object):
        ????def?__init__(self,?lr,?weight_decay):
        ????????self.lr?=?lr
        ????????self.weight_decay?=?weight_decay

        ????def?compute_step(self,?grads,?params):
        ????????step?=?list()
        ????????#?flatten?all?gradients
        ????????flatten_grads?=?np.concatenate(
        ????????????[np.ravel(v)?for?grad?in?grads?for?v?in?grad.values()])
        ????????#?compute?step
        ????????flatten_step?=?self._compute_step(flatten_grads)
        ????????#?reshape?gradients
        ????????p?=?0
        ????????for?param?in?params:
        ????????????layer?=?dict()
        ????????????for?k,?v?in?param.items():
        ????????????????block?=?np.prod(v.shape)
        ????????????????_step?=?flatten_step[p:p+block].reshape(v.shape)
        ????????????????_step?-=?self.weight_decay?*?v
        ????????????????layer[k]?=?_step
        ????????????????p?+=?block
        ????????????step.append(layer)
        ????????return?step

        ????def?_compute_step(self,?grad):
        ????????raise?NotImplementedError

        class?Adam(BaseOptimizer):
        ????def?__init__(self,?lr=0.001,?beta1=0.9,?beta2=0.999,
        ?????????????????eps=1e-8,?weight_decay=0.0)
        :

        ????????super().__init__(lr,?weight_decay)
        ????????self._b1,?self._b2?=?beta1,?beta2
        ????????self._eps?=?eps

        ????????self._t?=?0
        ????????self._m,?self._v?=?0,?0

        ????def?_compute_step(self,?grad):
        ????????self._t?+=?1
        ????????self._m?=?self._b1?*?self._m?+?(1?-?self._b1)?*?grad
        ????????self._v?=?self._b2?*?self._v?+?(1?-?self._b2)?*?(grad?**?2)
        ????????#?bias?correction
        ????????_m?=?self._m?/?(1?-?self._b1?**?self._t)
        ????????_v?=?self._v?/?(1?-?self._b2?**?self._t)
        ????????return?-self.lr?*?_m?/?(_v?**?0.5?+?self._eps)

        model

        最后 model 類實(shí)現(xiàn)了我們一開始設(shè)計(jì)的三個(gè)接口 forward、backward 和 apply_grad ,forward 直接調(diào)用 net 的 forward ,backward 中把 net 、loss、optimizer 串起來(lái),先計(jì)算損失 loss,然后反向傳播得到梯度,然后 optimizer 計(jì)算步長(zhǎng),最后由 apply_grad 對(duì)參數(shù)進(jìn)行更新

        #?model.py
        class?Model(object):
        ????def?__init__(self,?net,?loss,?optimizer):
        ????????self.net?=?net
        ????????self.loss?=?loss
        ????????self.optimizer?=?optimizer

        ????def?forward(self,?inputs):
        ????????return?self.net.forward(inputs)

        ????def?backward(self,?preds,?targets):
        ????????loss?=?self.loss.loss(preds,?targets)
        ????????grad?=?self.loss.grad(preds,?targets)
        ????????grads?=?self.net.backward(grad)
        ????????params?=?self.net.get_parameters()
        ????????step?=?self.optimizer.compute_step(grads,?params)
        ????????return?loss,?step

        ????def?apply_grad(self,?grads):
        ????????for?grad,?(param,?_)?in?zip(grads,?self.net.get_params_and_grads()):
        ????????????for?k,?v?in?param.items():
        ????????????????param[k]?+=?grad[k]

        整體結(jié)構(gòu)

        最后我們實(shí)現(xiàn)出來(lái)核心代碼部分文件結(jié)構(gòu)如下

        tinynn
        ├── core
        │ ├── initializer.py
        │ ├── layer.py
        │ ├── loss.py
        │ ├── model.py
        │ ├── net.py
        │ └── optimizer.py

        其中 initializer.py 這個(gè)模塊上面沒(méi)有展開講,主要實(shí)現(xiàn)了常見的參數(shù)初始化方法(零初始化、Xavier 初始化、He 初始化等),用于給網(wǎng)絡(luò)層初始化參數(shù)。

        MNIST 例子

        框架基本搭起來(lái)后,我們找一個(gè)例子來(lái)用 tinynn 這個(gè)框架 run 起來(lái)。這個(gè)例子的基本一些配置如下

        • 數(shù)據(jù)集:MNIST(http://yann.lecun.com/exdb/mnist/
        • 任務(wù)類型:多分類
        • 網(wǎng)絡(luò)結(jié)構(gòu):三層全連接 INPUT(784) -> FC(400) -> FC(100) -> OUTPUT(10),這個(gè)網(wǎng)絡(luò)接收 的輸入,其中 是每次輸入的樣本數(shù),784 是每張 的圖像展平后的向量,輸出維度為 ,其中 是樣本數(shù),10 是對(duì)應(yīng)圖片在 10 個(gè)類別上的概率
        • 激活函數(shù):ReLU
        • 損失函數(shù):SoftmaxCrossEntropy
        • optimizer:Adam(lr=1e-3)
        • batch_size:128
        • Num_epochs:20

        這里我們忽略數(shù)據(jù)載入、預(yù)處理等一些準(zhǔn)備代碼,只把核心的網(wǎng)絡(luò)結(jié)構(gòu)定義和訓(xùn)練的代碼貼出來(lái)如下

        #?example/mnist/run.py
        net?=?Net([
        ??Dense(784,?400),
        ??ReLU(),
        ??Dense(400,?100),
        ??ReLU(),
        ??Dense(100,?10)
        ])
        model?=?Model(net=net,?loss=SoftmaxCrossEntropyLoss(),?optimizer=Adam(lr=args.lr))

        iterator?=?BatchIterator(batch_size=args.batch_size)
        evaluator?=?AccEvaluator()
        for?epoch?in?range(num_ep):
        ????for?batch?in?iterator(train_x,?train_y):
        ???????#?training
        ????????pred?=?model.forward(batch.inputs)
        ????????loss,?grads?=?model.backward(pred,?batch.targets)
        ????????model.apply_grad(grads)
        ????#?evaluate?every?epoch
        ????test_pred?=?model.forward(test_x)
        ????test_pred_idx?=?np.argmax(test_pred,?axis=1)
        ????test_y_idx?=?np.asarray(test_y)
        ????res?=?evaluator.evaluate(test_pred_idx,?test_y_idx)
        ????print(res)

        運(yùn)行結(jié)果如下

        # tinynn
        Epoch 0 {'total_num': 10000, 'hit_num': 9658, 'accuracy': 0.9658}
        Epoch 1 {'total_num': 10000, 'hit_num': 9740, 'accuracy': 0.974}
        Epoch 2 {'total_num': 10000, 'hit_num': 9783, 'accuracy': 0.9783}
        Epoch 3 {'total_num': 10000, 'hit_num': 9799, 'accuracy': 0.9799}
        Epoch 4 {'total_num': 10000, 'hit_num': 9805, 'accuracy': 0.9805}
        Epoch 5 {'total_num': 10000, 'hit_num': 9826, 'accuracy': 0.9826}
        Epoch 6 {'total_num': 10000, 'hit_num': 9823, 'accuracy': 0.9823}
        Epoch 7 {'total_num': 10000, 'hit_num': 9819, 'accuracy': 0.9819}
        Epoch 8 {'total_num': 10000, 'hit_num': 9820, 'accuracy': 0.982}
        Epoch 9 {'total_num': 10000, 'hit_num': 9838, 'accuracy': 0.9838}
        Epoch 10 {'total_num': 10000, 'hit_num': 9825, 'accuracy': 0.9825}
        Epoch 11 {'total_num': 10000, 'hit_num': 9810, 'accuracy': 0.981}
        Epoch 12 {'total_num': 10000, 'hit_num': 9845, 'accuracy': 0.9845}
        Epoch 13 {'total_num': 10000, 'hit_num': 9845, 'accuracy': 0.9845}
        Epoch 14 {'total_num': 10000, 'hit_num': 9835, 'accuracy': 0.9835}
        Epoch 15 {'total_num': 10000, 'hit_num': 9817, 'accuracy': 0.9817}
        Epoch 16 {'total_num': 10000, 'hit_num': 9815, 'accuracy': 0.9815}
        Epoch 17 {'total_num': 10000, 'hit_num': 9835, 'accuracy': 0.9835}
        Epoch 18 {'total_num': 10000, 'hit_num': 9826, 'accuracy': 0.9826}
        Epoch 19 {'total_num': 10000, 'hit_num': 9819, 'accuracy': 0.9819}

        可以看到測(cè)試集 accuracy 隨著訓(xùn)練進(jìn)行在慢慢提升,這說(shuō)明數(shù)據(jù)在框架中確實(shí)按照正確的方式進(jìn)行流動(dòng)和計(jì)算,參數(shù)得到正確的更新。為了對(duì)比下效果,我用 Tensorflow 1.13 實(shí)現(xiàn)了相同的網(wǎng)絡(luò)結(jié)構(gòu)、采用相同的采數(shù)初始化方法、優(yōu)化器配置等等,得到的結(jié)果如下

        # Tensorflow 1.13.1
        Epoch 0 {'total_num': 10000, 'hit_num': 9591, 'accuracy': 0.9591}
        Epoch 1 {'total_num': 10000, 'hit_num': 9734, 'accuracy': 0.9734}
        Epoch 2 {'total_num': 10000, 'hit_num': 9706, 'accuracy': 0.9706}
        Epoch 3 {'total_num': 10000, 'hit_num': 9756, 'accuracy': 0.9756}
        Epoch 4 {'total_num': 10000, 'hit_num': 9722, 'accuracy': 0.9722}
        Epoch 5 {'total_num': 10000, 'hit_num': 9772, 'accuracy': 0.9772}
        Epoch 6 {'total_num': 10000, 'hit_num': 9774, 'accuracy': 0.9774}
        Epoch 7 {'total_num': 10000, 'hit_num': 9789, 'accuracy': 0.9789}
        Epoch 8 {'total_num': 10000, 'hit_num': 9766, 'accuracy': 0.9766}
        Epoch 9 {'total_num': 10000, 'hit_num': 9763, 'accuracy': 0.9763}
        Epoch 10 {'total_num': 10000, 'hit_num': 9791, 'accuracy': 0.9791}
        Epoch 11 {'total_num': 10000, 'hit_num': 9773, 'accuracy': 0.9773}
        Epoch 12 {'total_num': 10000, 'hit_num': 9804, 'accuracy': 0.9804}
        Epoch 13 {'total_num': 10000, 'hit_num': 9782, 'accuracy': 0.9782}
        Epoch 14 {'total_num': 10000, 'hit_num': 9800, 'accuracy': 0.98}
        Epoch 15 {'total_num': 10000, 'hit_num': 9837, 'accuracy': 0.9837}
        Epoch 16 {'total_num': 10000, 'hit_num': 9811, 'accuracy': 0.9811}
        Epoch 17 {'total_num': 10000, 'hit_num': 9793, 'accuracy': 0.9793}
        Epoch 18 {'total_num': 10000, 'hit_num': 9818, 'accuracy': 0.9818}
        Epoch 19 {'total_num': 10000, 'hit_num': 9811, 'accuracy': 0.9811}

        可以看到兩者效果上大差不差,測(cè)試集準(zhǔn)確率都收斂到 0.982 左右,就單次的實(shí)驗(yàn)看比 Tensorflow 稍微好一點(diǎn)點(diǎn)。

        總結(jié)

        tinynn 相關(guān)的源代碼在這個(gè) repo(https://github.com/borgwang/tinynn) 里。目前支持:

        • layer :全連接層、2D 卷積層、 2D反卷積層、MaxPooling 層、Dropout 層、BatchNormalization 層、RNN 層以及 ReLU、Sigmoid、Tanh、LeakyReLU、SoftPlus 等激活函數(shù)
        • loss:SigmoidCrossEntropy、SoftmaxCrossEntroy、MSE、MAE、Huber
        • optimizer:RAam、Adam、SGD、RMSProp、Momentum 等優(yōu)化器,并且增加了動(dòng)態(tài)調(diào)節(jié)學(xué)習(xí)率 LRScheduler
        • 實(shí)現(xiàn)了 mnist(分類)、nn_paint(回歸)、DQN(強(qiáng)化學(xué)習(xí))、AutoEncoder 和 DCGAN (無(wú)監(jiān)督)等常見模型。見 tinynn/examples:https://github.com/borgwang/tinynn/tree/master/examples

        tinynn 還有很多可以繼續(xù)完善的地方受限于時(shí)間還沒(méi)有完成,筆者在空閑時(shí)間會(huì)進(jìn)行維護(hù)和更新。

        當(dāng)然 tinynn 只是一個(gè)「玩具」版本的深度學(xué)習(xí)框架,一個(gè)成熟的深度學(xué)習(xí)框架至少還需要:支持自動(dòng)求導(dǎo)、高運(yùn)算效率(靜態(tài)語(yǔ)言加速、支持 GPU 加速)、提供豐富的算法實(shí)現(xiàn)、提供易用的接口和詳細(xì)的文檔等等。這個(gè)小項(xiàng)目的出發(fā)點(diǎn)更多地是學(xué)習(xí),在設(shè)計(jì)和實(shí)現(xiàn) tinynn 的過(guò)程中筆者個(gè)人學(xué)習(xí)確實(shí)到了很多東西,包括如何抽象、如何設(shè)計(jì)組件接口、如何更效率的實(shí)現(xiàn)、算法的具體細(xì)節(jié)等等。對(duì)筆者而言寫這個(gè)小框架除了了解深度學(xué)習(xí)框架的設(shè)計(jì)與實(shí)現(xiàn)之外還有一個(gè)好處:后續(xù)可以在這個(gè)框架上快速地實(shí)現(xiàn)一些新的算法,新的參數(shù)初始化方法,新的優(yōu)化算法,新的網(wǎng)絡(luò)結(jié)構(gòu)設(shè)計(jì),都可以快速地在這個(gè)小框架上進(jìn)行實(shí)驗(yàn)。如果你對(duì)自己設(shè)計(jì)實(shí)現(xiàn)一個(gè)深度學(xué)習(xí)框架也感興趣,希望看完這篇文章會(huì)對(duì)你有所幫助,也歡迎大家提 PR 一起貢獻(xiàn)代碼~

        附錄: Softmax 交叉熵?fù)p失和梯度推導(dǎo)

        多分類下交叉熵?fù)p失如下式:

        其中 分別是真實(shí)值和模型預(yù)測(cè)值, 是樣本數(shù), 是類別個(gè)數(shù)。由于真實(shí)值一般為一個(gè) one-hot 向量(除了真實(shí)類別維度為 1 其他均為 0),因此上式可以化簡(jiǎn)為

        其中 是代表真實(shí)類別, 代表第 個(gè)樣本 類的預(yù)測(cè)概率。即我們需要計(jì)算的是每個(gè)樣本在真實(shí)類別上的預(yù)測(cè)概率的對(duì)數(shù)的和,然后再取負(fù)就是交叉熵?fù)p失。接下來(lái)推導(dǎo)如何求解該損失關(guān)于模型輸出的梯度,用 表示模型輸出,在多分類中通常最后會(huì)使用 Softmax 將網(wǎng)絡(luò)的輸出歸一化為一個(gè)概率分布,則 Softmax 后的輸出為

        代入上面的損失函數(shù)

        求解 關(guān)于輸出向量 的梯度,可以將 分為目標(biāo)類別所在維度 和非目標(biāo)類別維度 。首先看目標(biāo)類別所在維度
        再看非目標(biāo)類別所在維度

        可以看到對(duì)于目標(biāo)類別維度,其梯度為對(duì)應(yīng)維度模型輸出概率減一,對(duì)于非目標(biāo)類別維度,其梯度為對(duì)應(yīng)維度輸出概率真身。

        參考

        • Deep Learning, Goodfellow, et al. (2016)
        • Joel Grus - Livecoding Madness - Let's Build a Deep Learning Library
        • TensorFlow Documentation
        • PyTorch Documentation
        瀏覽 68
        點(diǎn)贊
        評(píng)論
        收藏
        分享

        手機(jī)掃一掃分享

        分享
        舉報(bào)
        評(píng)論
        圖片
        表情
        推薦
        點(diǎn)贊
        評(píng)論
        收藏
        分享

        手機(jī)掃一掃分享

        分享
        舉報(bào)
        1. <strong id="7actg"></strong>
        2. <table id="7actg"></table>

        3. <address id="7actg"></address>
          <address id="7actg"></address>
          1. <object id="7actg"><tt id="7actg"></tt></object>
            鸡巴操逼网站 | 国产一级做受视频 | 免费看黄 片,在线观看 | 张柏芝亚洲一区二区三区 | 色色99 | 乱色精品无码一区二区国产盗 | 农村妇女做爰视频 | 亚洲国产精品精 | 性欧美bbbbbb图片 | 99精品人妻一区二区 |