猫咪都能看懂的神经网络原理!

0、免责声明:

整篇文章的书写目的是让猫咪学会神经网络原理,对于人类学习之后的后果一概不予承担。

喵 (*≧︶≦))( ̄▽ ̄* )ゞ

在本篇文章中,你将读到:落后的结构理解、与事实偏差的描述,以及不专业的猫咪用词。

1、什么是神经网络

粗犷地看,神经网络本质上是一个盒子:你先丢东西进去,把盒子盖上,再打开,盒子就会吐一些不同的玩意儿出来。

神经网络几乎可以做任何事情,比如『分辨柴犬和全麦面包』。这个案例的本质就是把一张照片丢进盒子里,盖上盖子,打开,然后盒子会吐出一张纸条,纸条上写着『柴犬!』或者『全麦面包!』

(想吃全麦面包((wwwww

神经网络领域的『Hello World』『分辨 0 ~ 9 的手写数字』。这篇文章也将以此为核心案例展开!

2、神经网络是如何运作的

经典的神经网络是一个由很多『层』叠起来的玩意,每层里有若干个掌管数据的神,名叫『神经元』。本质上,每个神经元都持有一个 0~1 之间的值(可以是小数),这个数值就是神经元的『状态』

图片是怎么进入网络的?

我们都知道,大部分图片由带颜色的像素(一个一个带颜色的小格子)组成。但是神经网络只能接受数字,那咋整嘞?其实好办:

  1. 先把所有的像素用某些特定的、一一对应(一个数表示一种颜色)的数表示出来,
  2. 再从左到右、从上到下把图片切成一条一条(每条一个像素粗),
  3. 整个图片就可以被连成一条串串:包含所有的像素所对应的数。

在这个案例里,我们要分辨 14 × 14 像素的黑白图片里写着什么数字。

由于黑白图片都是白底黑字的,所以我们只需要一个 0 ~ 1 之间的数(代表颜色黑的程度,0 是白色,1 是黑色,其中的是不同程度的灰色)——就能表示一个像素了。

(如果是彩色图片可能还要做更多处理……

(但是咱们这个黑白图片甚至还没有灰色,

(所以会简单更多…… = ̄ω ̄=)

为了让数字更好看,Liny 将所有的 0~1 乘了 100,变成了 0~100之间的数(如下图所示)。

0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00

0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00

0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00

0.00 0.00 0.00 0.00 0.00 0.00 100 100 100 0.00 0.00 0.00 0.00 0.00

0.00 0.00 0.00 0.00 100 100 100 0.00 100 100 0.00 0.00 0.00 0.00

0.00 0.00 0.00 0.00 100 100 0.00 0.00 0.00 100 0.00 0.00 0.00 0.00

0.00 0.00 0.00 0.00 100 0.00 0.00 0.00 0.00 100 0.00 0.00 0.00 0.00

0.00 0.00 0.00 0.00 100 0.00 0.00 0.00 0.00 100 0.00 0.00 0.00 0.00

0.00 0.00 0.00 0.00 100 0.00 0.00 0.00 100 0.00 0.00 0.00 0.00 0.00

0.00 0.00 0.00 0.00 100 0.00 0.00 100 100 0.00 0.00 0.00 0.00 0.00

0.00 0.00 0.00 0.00 100 100 100 100 0.00 0.00 0.00 0.00 0.00 0.00

0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00

0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00

0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00

Fig. 2.1: 写了 “0” 的图片被编成数字之后的结果(写论文写疯了(bushi

现在,猫咪把这张写着 “0” 的图片丢进神经网络里,会发生什么呢?(喵~咻

数据是怎么被处理的?

我们知道,神经网络有很多层,这些层上有很多神经元。

层和神经元是这么工作的:

每层的每个神经元都会去了解上层每个神经元的状态,再将自己被设定的某些规则套到了解到的上层状态上,捏一个主意,决定自己变成什么样子。

然后它们再下一层的神经元也会读到它们的状态,继续决定下一层变成什么样子……

至此,实现数据的传递和整理。

一般来说,神经网络的第一层(输入层)就是丢进盒子的那些数据本身(不做处理)。

在这个案例里,也就是说,第一层就需要 14 × 14 = 196 个神经元来装这些数字。(这些第一层的神经元一般什么活都不用干,只是持有数据,定定地坐在那里,等待下一层的神经元来了解状态。)

从第二层开始,事情就变得有意思了!

第二次的每个神经元会了解第一层(也就是刚刚提到过,有着 196 个神经元的输入层)所有神经元的状态。

每个神经元会有自己的一套『法则』,对得到的一长串状态数据进行处理。

举例子的话……这套法则可能是:丢掉前 25 个(前 25 个乘 0),丢掉后 25 个(后 25 个乘 0),剩下的某些乘 2,某些乘 -1,某些乘…… 总的来说,就是给上层的每个神经元状态乘一个特定的数,这个数就叫做『权重』

然后把这些得到的结果加在一起,就能得到一个有特点的数据。

怎么个有特点法呢?

比如说,如果某一个神经元要检测图像里有没有一条横,那么它在的法则里,应该出现一条横的地方就要格外重视,那些代表【应该被涂成黑色的像素】的上层神经元状态就要保留(并且要大力鼓励喵!),所以把那些上层神经元的权重设置成比较大的正数。

(好长的句子,要断气了,呜喵 ( ̄ ‘i  ̄;) )

至于那些神经元认为不应该画线的区域,就要狠狠地惩罚——因此把这些神经元的权重设置成比较夸张的负数。

这样,这个神经元根据自己的法则,总结了上层神经元状态的某一个特点——

如果上层神经元有那么一条横,那么自己最终加起来得到那一个数就会是正的,并且会很大;

如果上层神经元没有那么一条横,那么自己得到的数可能会很小,可能很接近 0;

如果上层神经元非常不巧,雷区蹦迪,在自己认为不该变黑的像素区域到处乱涂,那么自己得到的数可能会非常地,…… 就……挺生气的…… (╯▔皿▔)╯

这样,每个神经元就依靠自己的法则,通过了解上一层神经元的状态,总结出一些什么东西来(大概率是人总结不出来的抽象玩意儿,但是能用(?),表现在自己持有的数上,展示给下一层看。

这其中还有一些其他过程:

偏置:

在把所有上层状态和对应的权重分别相乘、然后相加后(暂时管这个直接得到的数叫 R 吧),会再加上一个数,这个数就是『偏置』

作用(粗犷地理解)是让这个数字达到一定标准。

比如说,希望对 R 严格要求,要求它大于十之后神经元得到的结果才是正数。

这时就可以把 R 先 – 10,这样 R 只有满足 R > 10 的标准时,这个神经元得到的真正结果才是正的。

激活函数

我们知道,所有的神经元持有一个 0 ~ 1 之间的数作为它的状态。但是很显然,我们把上边提到那些东西加起来得到的结果,大概率不在这个范围内。

为了解决这个问题,每个神经元处理所得数据的最后一步是使用『激活函数』

(为啥叫这个名字…… Liny 同样也不知道((( ¯\(ツ)_/¯

激活函数的作用就是把任意一个数压缩成一个 0~1 之间的数。

比如这个:Sigmoid(x) = (1 + e^-x)^(-1)(本篇文章第一句不说人话的话 ( •_•)>⌐■-■(((

把 x 想象成任何一个数,经过这样的计算,总能得到一个 0~1 之间的数。并且,x 自己越大,得到的结果就越靠近 1,x 自己越小,结果就越靠近 0。

┏ (゜ω゜)=👉 关于 Sigmoid 函数的更多……喵!

把 R(那个我们暂时管叫的、直接相加的结果)加上偏置后,再整个丢进激活函数里,就能得到一个数,代表这个神经元自己的状态啦!

层层递进……让我们继续下去!

神经网络中,每层每个神经元都会有不同的权重和偏置,因此它们即便了解到了同样的上层神经元状态,它们最终得到的结果也是不一样的(都能,在某种程度上,反映各个神经元对上层数据关心的侧重点)。

当网络有好几层的时候,丢进网络里的那个东西(那堆数据)会被各个神经元依照各自的法则分析,总结出丢进来的数据的抽象特点,最终,在最后一层直接表现出来……

比如,第二层网络经过缜密思考、精确判断,得到了丢进网络的那个图片里有没有横、有没有竖、有没有圈圈……下一层,第三层的网络再经过合理推断、组合理解,得到,比如说,那个图片里有没有十字(横和竖的结合)……下一层……下一层……

(但是大概率不会像这样,真的是横啊竖啊什么的……

(神经元们总结的特点都很抽象,人,和猫咪,大概率,都,理解不能……( ⓛ ω ⓛ * ||||、)

在这个案例里,Liny 选择在第二层、第三层分别各放置 8 个神经元(随便放的,没有依据(((

最后,在最后一层放置十个,让他们分别代表 0~9 的十个数字。神经网络归纳整理的结果就直接体现在这十个神经元的状态上:哪个状态数大,就代表神经网络认为图片是哪个数字的概率高。

至此,我们已经知道神经网络如何接受一张图片,并且输出一些数字作为结果了。

3、神经网络是如何训练的

从本质上来说,神经网络的训练和学习,就是不断寻找所有偏置和权重(管这些统一称呼为『参数』)最合适的大小,让神经网络能够稳定地分辨,给出正确的结果。

那么,怎么得到这样的参数呢?

对于人(和猫咪)来说,想要学会一样东西,就要有明确的目标和方向。

人类会被要求好好学习——那么标准就是考试分数,分数越高越好。

猫咪会被要求乖乖听话——那么标准就是挠坏的沙发,沙发越少越好。

对于这样的神经网络来说,标准就是『正确率』和『损失』

正确率:

猫咪都能理解——就是拿一溜儿东西,依次丢进神经网络里,并且依次得到神经网络吐出来的结果。如果一个结果和人所期待的相同,让人高兴了,就称这个结果是“正确”的。

所有次尝试(丢进去东西,拿到结果)中正确的占总数的比,就是『正确率』

损失:

在这个案例里,每次丢一个图片进神经网络里,神经网络就会给出十个数(分别代表图片里写了 0~9 的可能性)。一般来说,最终的结果就是这十个数里最大的那个所代表的数字。

比如,

得到结果:0.2, 0.2,0.1, 0.3, 0.3, 0.2,0.4, 0.0,0.1,0.8

分别代表:0, 1, 2, 3, 4, 5, 6, 7, 8, 9

那么很显然,最终的结果就是 “9” ——神经网络认为图片里写的就是数字 “9”。

但是这个网络很显然,表现得还不够好——它还有点不确定。

比如,它认为这个还可能是 “6”,因为代表 “6” 的数依然高达 0.4。

我们所希望的,应该是这样一个结果:(如果图片里真的写了 “9” 的话 :)

0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,1.0

那么,怎么衡量神经网络表现得好、或者不好的程度呢?

方法就是,用一个像考试分数一样的数——『损失』

一种损失率的计算方法:

神经网络得到的结果里,每一个数分别减去预期应该达到的数,再平方

把 10 个数这么计算的差的平方加起来,就能得到一个总体的、代表目前这个结果距目标差距的、

这个数就叫做『损失(Loss)』(为啥叫这个名字…… Liny 也不知道(¯\_(ツ)_/¯

在这种情况下,整个神经网络,包括数据集,就变成了一个巨大的函数(巨大的盒子):输入(丢进去的东西)是这套参数,输出(盒子吐出来的东西)是这套参数的损失——这套参数的好坏程度。

参数的评估

那么我们怎么评估一套参数的好坏呢?自然就是通过丢大量的东西(就是数据的集合,叫做『数据集』)给神经网络,再看看它给出的大量结果,计算出整个薯聚集(what?输入法,坏,划掉,并且,想吃薯条……欸嘿嘿,薯条,嘿嘿……嘿嘿嘿…… (★ ω ★))数据集的正确率和平均损失。

一般来说,损失越低,正确率越高,这套参数就越好。

并且,一般来说,损失越低,正确率应该就会越高。(有兴趣的可以研究下为啥…… ( •̀ ω •́ )✧

参数的训练!正片开始,绝赞讲解中!(whar

一般来说,是这样的:先把所有的参数设置成随机的,跑一次试试(看看运气(bushi

不过确实是这样的——从一个随机的地方开始。

现在,我们怎么知道,如何调整这些参数,让它的损失一点点降低呢?

最朴实无华的方式:试一下就知道了!(其实应该用多元微积分和偏导的(第二句不是人话(

分别调整所有参数,每次加一点点,然后计算调整后的平均损失,和调整前作对比。

这样,我们会得到一个表(近似于含有所有参数偏导的表(第三句不是人话((汗((,代表每个参数加了一点点后对整体平均损失的影响——(先这么理解着吧……可能误差有点大……不过没关系 😛 大概吧……

聚焦到某一个参数上:调整它(加了一点点)以后,

如果损失变小了,那么说明这个参数就该加,在接下来的调整中就应该把它变大;

如果损失变大了,那么说明这个参数不能加,得反过来,减小它。

这样,根据得到的表,依次调整所有的参数,就能得到新的一套参数。

然后再评估一次它的好坏,再算一次这个表,再调整,再评估,再算表,再调整……

以此往复,便能逐渐降低损失,一步一步得到更好的参数!

总有一天,这套参数将被调整到接近完美——损失极低,正确率极高,能够准确分辨几乎所有的手写数字(让 Liny 可以安心吃薯条了(bushi(( o= > ェ•)o ┏━┓

到那天,如果你成功了,请你告诉 Liny,让 Liny 也来观摩一下……

更多,关于梯度下降和局部最优解(又不讲人话了(第四句(((

这部分仅讲给聪明的猫咪:

我们这个优化的方法叫做梯度下降法,有点像下山。

我们从随机初始值一步一步降低损失得到的、接近完美的一套参数并不是全局的最优解,而只是局部的——

这就像,我们随机出现在某喀斯特地貌地区的某一个山头,只知道往下走,走到一个附近一坑里就停了。但是,这个坑只是我们顺着山坡向下走的结果,并不是整片喀斯特地貌地区海拔最低的坑——

可能在某个我们完全看不到的地方,存在一个更好的解。

因此,这篇文章所介绍的方法只能让你得到一个看起来不错的答案(实际上很可能还存在更棒的答案,只是时运不济,还没找着(((#`-_ゝ-)

所以,祝大家好运!(如果你真的要照着这个写得很烂的文章做的话……(((*゜ー゜*)

4、尾声

在写这篇文章的时候,Liny 同时在用 Python(Scratch 也可以,只是慢一点)训练一套参数,以完成 0~9 所有数字的分辨…… 真的好慢喔(不该用这么淳朴的方法的(((明明有那么多可以用的库(((

关于数据:

任何人都可以使用这个工具制造自己的手写数字数据集:

(请你下载下来用,因为直接运行列表不能导出(((( o( ̄┰ ̄*)ゞ

关于训练:

一般建议使用高效率的方法训练。如果你要折磨自己的话,可以使用这个:

如果你要高一点的效率,可以考虑使用 Python。

这是一个没有外部库的案例:如果你真的要用,那么你需要自己写 dataset.txt(或者从别的地方搞一个)、params.txt(或者随机一个)

dataset.txt 的格式示例:[目标数字] [14 * 14 = 196 个数,代表图片的像素]

0 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 100 100 100 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 100 100 100 0.00 100 100 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 100 100 0.00 0.00 0.00 100 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 100 0.00 0.00 0.00 0.00 100 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 100 0.00 0.00 0.00 0.00 100 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 100 0.00 0.00 0.00 100 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 100 0.00 0.00 100 100 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 100 100 100 100 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00
......

代码:(如果你要更高的效率请你考虑 Torch 或者别的成熟的第三方库 ( •_•)>⌐■-■)

import math
import random
import copy
import time


def random_params():
    random_start = []
    for i in range(1738):
        random_start.append(random.randint(-5, 5))
    return random_start


DATASET = open('dataset.txt', 'r').read().split("\n")
# PARAMS = random_params()
PARAMS = open('params.txt', 'r').read().split("\n")
PARAMS = [float(awa) for awa in PARAMS]

layer_i_1 = []
layer_1_2 = []
layer_2_o = []

data_1 = []
data_2 = []
data_o = []


def sigmoid(input_value: float):
    """

    :param input_value: The float
    :return: A value from 0 ~ 1
    """
    # print(input_value)
    if input_value > 1000:
        return 1
    if input_value < -1000:
        return 0
    return 1 / (1 + (math.pow(math.e, -0.1 * input_value)))


def load_params(params: list[float]):
    """

    :param params: The big list of a total of 1738 floats
    """

    global layer_i_1
    global layer_1_2
    global layer_2_o
    layer_i_1 = params[0:1576]
    layer_1_2 = params[1576:1648]
    layer_2_o = params[1648:1738]


def recognize(params: list[float], input_layer: list[float]):
    """

    :param params: The Params
    :param input_layer: A plain list of 196 floats
    :return: [result_sorted_by_possibility, data_of_output_layer]
    """

    load_params(params)

    # print(input_layer)
    input_layer = [float(awa) for awa in input_layer]

    global data_o
    global data_1
    global data_2
    global layer_i_1
    global layer_1_2
    global layer_2_o

    data_1 = []
    data_2 = []
    data_o = []

    for out in range(8):
        neuron_sum = 0
        neuron_weight = layer_i_1[out * 197:(out + 1) * 197 - 1]
        neuron_weight = [float(awa) for awa in neuron_weight]

        neuron_bias = float(layer_i_1[(out + 1) * 197 - 1])
        for inner in range(196):
            neuron_sum += neuron_weight[inner] * input_layer[inner]
        data_1.append(sigmoid(neuron_sum + neuron_bias))

    for out in range(8):
        neuron_sum = 0
        neuron_weight = layer_1_2[out * 9:(out + 1) * 9 - 1]
        neuron_weight = [float(awa) for awa in neuron_weight]

        neuron_bias = float(layer_1_2[(out + 1) * 9 - 1])
        for inner in range(8):
            neuron_sum += neuron_weight[inner] * data_1[inner]
        data_2.append(sigmoid(neuron_sum + neuron_bias))

    for out in range(10):
        neuron_sum = 0
        neuron_weight = layer_2_o[out * 9:(out + 1) * 9 - 1]
        neuron_weight = [float(awa) for awa in neuron_weight]

        neuron_bias = float(layer_2_o[(out + 1) * 9 - 1])
        for inner in range(8):
            neuron_sum += neuron_weight[inner] * data_2[inner]
        data_o.append(sigmoid(neuron_sum + neuron_bias))

    result_sorted = []
    sort_temp = copy.deepcopy(data_o)
    for k in range(10):
        sort_max = 0
        for a in range(len(sort_temp)):
            if sort_temp[a] > sort_temp[sort_max]:
                sort_max = a
        result_sorted.append(sort_max)
        sort_temp[sort_max] = -1

    return [result_sorted, data_o]


def loss_acc_of(params: list[float], data_slice: str):
    """

    :param params: The Params
    :param data_slice: The slice of data (a line of str) from a set containing the target_digit in the first item
    :return: [loss, is_accurate]
    """

    # Filter out target_digit and real_data
    data_sliced = data_slice.split(" ")
    target_digit = int(data_sliced[0])
    real_data = [float(awa) for awa in data_sliced[1:197]]

    # Generates expect_loss
    expect_loss = []
    for k in range(10):
        if k == target_digit:
            expect_loss.append(1)
        else:
            expect_loss.append(0)

    # Get recognize result
    result = recognize(params, real_data)
    result_acc = result[0][0] == target_digit  # See if it's correct or not
    result_loss = result[1]

    # Count total loss
    loss = 0
    for item in range(10):
        loss += math.pow((expect_loss[item] - result_loss[item]), 2)

    return [loss, result_acc]


def loss_acc_avg_of_set(params: list[float], dataset: list[str]):
    """

    :param params: The Params
    :param dataset: The whole set of lines of data in str
    :return: A list 2 floatings: [average_loss, accuracy]
    """

    total_loss = 0
    total_acc = 0
    dataset_size = len(dataset)

    for i in range(dataset_size):
        l_a = loss_acc_of(params, dataset[i])
        total_loss += l_a[0]
        total_acc += l_a[1]

    return [total_loss / dataset_size, total_acc / dataset_size]


def get_gradients(params: list[float], dataset: list[str], step: float):
    """

    :param params: The Params
    :param dataset: The Dataset
    :param step: The value of unit increment
    :return: The list of rough gradients
    """

    original_loss = loss_acc_avg_of_set(params, dataset)[0]

    params_size = len(params)
    loss_list = []
    for i in range(params_size):
        adjusted_params = copy.deepcopy(params)
        adjusted_params[i] = adjusted_params[i] + step
        gradient = loss_acc_avg_of_set(adjusted_params, dataset)[0] - original_loss
        loss_list.append(gradient)
        if i % 100 == 0:
            print("[GRADIENTS] " + str(i) + " / " + str(params_size))
    return loss_list


def adjust_params(params: list[float], list_gradients: list[float], step: float):
    """

    :param params: The Params
    :param list_gradients: The Gradients
    :param step: The value of each increment
    :return: The adjusted params
    """
    for i in range(len(list_gradients)):
        params[i] += -1 * step * gradients[i]
    return params


# print(recognize(PARAMS, DATASET[0].split()[1:197]))
# print(loss_acc_of(PARAMS, DATASET[0]))
# print(loss_acc_avg_of_set(PARAMS, DATASET))

time_start = time.time()

loss_and_acc = loss_acc_avg_of_set(PARAMS, DATASET)
new_loss = loss_and_acc[0]
new_acc = loss_and_acc[1]
print("Starting from: ")
print("[LOSS_] " + str(new_loss))
print("[ACC_] " + str(new_acc))
print("LINK START!\n\n")

# Auto training for 100 Steps
for out_cycle in range(250):
    gradients = get_gradients(PARAMS, DATASET, 0.3)

    new_loss = 0
    new_acc = 0
    last_loss = 999
    for in_cycle in range(5000):
        PARAMS = adjust_params(PARAMS, gradients, 5)
        loss_and_acc = loss_acc_avg_of_set(PARAMS, DATASET)
        new_loss = loss_and_acc[0]
        new_acc = loss_and_acc[1]
        if in_cycle % 50 == 0:
            print("[LOSS_" + str(in_cycle) + "] " + str(last_loss) + " → " + str(new_loss))
            print("[ACC_" + str(in_cycle) + "] " + str(new_acc))
        if new_loss > last_loss:
            break
        last_loss = new_loss

    fout = open('output/0_9_' + str(round(new_loss * 10000) / 10000) + '_' + str(new_acc) + '.txt', 'w')
    fout.write("\n".join([str(awa) for awa in PARAMS]))
    fout.close()

    print("[STEP] " + str(time.time() - time_start) + "s (" +
          str((time.time() - time_start) / 3600) + "h) "
          "Taken to reach the " + str(out_cycle) + "th Step")

# End of programme

鸣谢 & 参考(Bibliography(?)

3Blue1Brown:线性代数的本质

Grant Sanderson:多元微积分

3Blue1Brown:神经网络的结构

3Blue1Brown:深度学习之梯度下降法

最后、最后的话……

这篇文章写出来太折磨了…… 编辑器一大堆 bug,写着写着添加的作品卡片就不见了(

写到后面精神状态不太准正常(现在是 0:49:55 ( ̄┰ ̄*))于是自言自语的情况加重了!!!

这篇文章也没有专门审阅了……有错误请指出……orz

不过言归正传:

喵~!现在你(可能)已经学会了基本的神经网络原理了!

它能带给你的生产生活什么新鲜玩意儿呢……似乎也没wwww

但是多知道点东西,总是好的吧「ฅ^•ω•^ฅ」

如果你做出来了什么成功案例,请务必用各种方式通知 Liny 过去观摩 ( •̀ ω •́ )✧

下次见!

暂无评论

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇