diff --git a/5_nn/3-softmax_ce.ipynb b/5_nn/3-softmax_ce.ipynb index cf9de55..6650f49 100644 --- a/5_nn/3-softmax_ce.ipynb +++ b/5_nn/3-softmax_ce.ipynb @@ -17,6 +17,37 @@ "\n", "softmax(柔性最大值)函数,一般在神经网络中, softmax可以作为分类任务的输出层。其实可以认为softmax输出的是几个类别选择的概率,比如我有一个分类任务,要分为三个类,softmax函数可以根据它们相对的大小,输出三个类别选取的概率,并且概率和为1。\n", "\n", + "Softmax从字面上来说,可以分成`soft`和`max`两个部分。`max`故名思议就是最大值的意思。Softmax的核心在于`soft`,而`soft`有软的含义,与之相对的是`hard`硬。很多场景中需要我们找出数组所有元素中值最大的元素,实质上都是求的`hardmax`。下面使用`Numpy`模块实现hardmax。" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "5\n" + ] + } + ], + "source": [ + "import numpy as np\n", + "\n", + "a = np.array([1, 2, 3, 4, 5]) # 创建ndarray数组\n", + "a_max = np.max(a)\n", + "print(a_max) # 5" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + "通过上面的例子可以看出hardmax最大的特点就是只选出其中一个最大的值,即非黑即白。但是往往在实际中这种方式是不合情理的,比如对于文本分类来说,一篇文章或多或少包含着各种主题信息,我们更期望得到文章对于每个可能的文本类别的概率值(置信度),可以简单理解成属于对应类别的可信度。所以此时用到了soft的概念,**Softmax的含义就在于不再唯一的确定某一个最大值,而是为每个输出分类的结果都赋予一个概率值,表示属于每个类别的可能性。**\n", + "\n", "softmax函数的公式是这种形式:\n", "\n", "$$\n", @@ -31,8 +62,13 @@ "\n", "![softmax_demo](images/softmax_demo.png)\n", "\n", - "softmax直白来说就是将原来输出是$[3,1,-3]$通过softmax函数一作用,就映射成为(0,1)的值,而这些值的累和为1(满足概率的性质),那么我们就可以将它理解成概率,在最后选取输出结点的时候,我们就可以选取概率最大(也就是值对应最大的)结点,作为我们的预测目标!\n", - "\n", + "softmax直白来说就是将原来输出是$[3,1,-3]$通过softmax函数作用,就映射成为(0,1)的值,而这些值的累和为1(满足概率的性质),那么我们就可以将它理解成概率,在最后选取输出结点的时候,我们就可以选取概率最大(也就是值对应最大的)结点,作为我们的预测目标!\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ "\n", "\n", "首先是神经元的输出,一个神经元如下图:\n", @@ -53,21 +89,54 @@ "a_i = \\frac{e^{z_i}}{\\sum_k e^{z_k}}\n", "$$\n", "\n", - "$a_i$代表softmax的第$i$个输出值,右侧套用了softmax函数。\n", + "$a_i$代表softmax的第$i$个输出值,右侧套用了softmax函数。\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 2. 交叉熵损失函数\n", + "\n", + "在神经网络反向传播中,需要设计一个损失函数,这个损失函数表示真实值与网络的估计值的误差,知道误差了,才能知道怎样去修改网络中的权重。\n", + "\n", + "神经网络的设计目的之一是为了使机器可以像人一样学习知识。**人在学习分析新事物时,当发现自己犯的错误越大时,改正的力度就越大**。比如投篮:当运动员发现自己的投篮方向离正确方向越远,那么他调整的投篮角度就应该越大,篮球就更容易投进篮筐。同理,我们希望:ANN在训练时,如果预测值与实际值的误差越大,那么在反向传播训练的过程中,各种参数调整的幅度就要更大,从而使训练更快收敛。然而,**如果使用二次代价函数训练ANN,看到的实际效果是,如果误差越大,参数调整的幅度可能更小,训练更缓慢。**\n", + " \n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "以一个神经元的二类分类训练为例,进行两次实验(神经网络常用的激活函数为`sigmoid`函数,该实验也采用该函数):输入一个相同的样本数据x=1.0(该样本对应的实际分类y=0);两次实验各自随机初始化参数,从而在各自的第一次前向传播后得到不同的输出值,形成不同的代价(误差):\n", + "\n", + "![cross_entropy_loss_1](images/cross_entropy_loss_1.png)\n", + "实验1:第一次输出值为0.82\n", + "\n", + "![cross_entropy_loss_2](images/cross_entropy_loss_2.png)\n", + "实验2:第一次输出值为0.98\n", + "\n", "\n", + "在实验1中,随机初始化参数,使得第一次输出值为0.82(该样本对应的实际值为0);经过300次迭代训练后,输出值由0.82降到0.09,逼近实际值。而在实验2中,第一次输出值为0.98,同样经过300迭代训练,输出值只降到了0.20。\n", "\n", - "### 1.1 损失函数 loss function\n", "\n", - "在神经网络反向传播中,要求一个损失函数,这个损失函数其实表示的是真实值与网络的估计值的误差,知道误差了,才能知道怎样去修改网络中的权重。\n", + "神经网络常用的激活函数为sigmoid函数,该函数的曲线如下所示:\n", + "![cross_entropy_loss_sigmod.png](images/cross_entropy_loss_sigmod.png)\n", "\n", + "如图所示,实验2的初始输出值(0.98)对应的梯度明显小于实验1的输出值(0.82),因此实验2的参数梯度下降得比实验1慢。这就是初始的代价(误差)越大,导致训练越慢的原因。与我们的期望不符,即:不能像人一样,错误越大,改正的幅度越大,从而学习得越快。" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ "损失函数可以有很多形式,这里用的是交叉熵函数,主要是由于这个求导结果比较简单,易于计算,并且交叉熵解决某些损失函数学习缓慢的问题。**[交叉熵函数](https://blog.csdn.net/u014313009/article/details/51043064)**是这样的:\n", "\n", "$$\n", "C = - \\sum_i y_i ln a_i\n", "$$\n", "\n", - "其中$y_i$表示真实的分类结果。\n", - "\n" + "其中$y_i$表示真实的分类结果。\n" ] }, { @@ -88,7 +157,7 @@ "\\frac{\\partial C}{\\partial z_i} = \\frac{\\partial C}{\\partial a_j} \\frac{\\partial a_j}{\\partial z_i}\n", "$$\n", "\n", - "有个人可能有疑问了,这里为什么是$a_j$而不是$a_i$,这里要看一下$softmax$的公式了,因为$softmax$公式的特性,它的分母包含了所有神经元的输出,所以,对于不等于i的其他输出里面,也包含着$z_i$,所有的$a$都要纳入到计算范围中,并且后面的计算可以看到需要分为$i = j$和$i \\ne j$两种情况求导。\n", + "有个人可能有疑问了,这里为什么是$a_j$而不是$a_i$,这里要看一下$softmax$的公式了,因为$softmax$公式的特性,它的分母包含了所有神经元的输出,所以,对于不等于$i$的其他输出里面,也包含着$z_i$,所有的$a$都要纳入到计算范围中,并且后面的计算可以看到需要分为$i = j$和$i \\ne j$两种情况求导。\n", "\n", "### 2.1 针对$a_j$的偏导\n", "\n", @@ -135,6 +204,36 @@ "cell_type": "markdown", "metadata": {}, "source": [ + "参数更新方程为\n", + "$$\n", + "\\frac{\\partial C}{\\partial w_{ij}} = (-y_i + a_i) x_i\n", + "$$\n", + "\n", + "其中\n", + "$$\n", + "z_i = \\sum_{j} w_{ij} x_{j} + b\n", + "$$\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "对于使用二次代价函数的更新方程为:\n", + "\n", + "$$\n", + "\\delta_i = a_i (1-a_i) (y_i - a_i)\n", + "$$\n", + "\n", + "$$\n", + "w_{ji} = w_{ji} + \\eta \\delta_j x_{ji}\n", + "$$" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ "## 3. 问题\n", "如何将本节所讲的softmax,交叉熵代价函数应用到上节所讲的BP方法中?" ] @@ -146,6 +245,7 @@ "## References\n", "\n", "* Softmax & 交叉熵\n", + " * [一文详解Softmax函数](https://zhuanlan.zhihu.com/p/105722023)\n", " * [交叉熵代价函数(作用及公式推导)](https://blog.csdn.net/u014313009/article/details/51043064)\n", " * [手打例子一步一步带你看懂softmax函数以及相关求导过程](https://www.jianshu.com/p/ffa51250ba2e)\n", " * [简单易懂的softmax交叉熵损失函数求导](https://www.jianshu.com/p/c02a1fbffad6)" diff --git a/5_nn/3-softmax_ce_EN.ipynb b/5_nn/3-softmax_ce_EN.ipynb index d5186cd..c2d2a29 100644 --- a/5_nn/3-softmax_ce_EN.ipynb +++ b/5_nn/3-softmax_ce_EN.ipynb @@ -166,7 +166,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.6.8" + "version": "3.6.9" } }, "nbformat": 4, diff --git a/5_nn/images/cross_entropy_loss_1.png b/5_nn/images/cross_entropy_loss_1.png new file mode 100644 index 0000000..46fd3f9 Binary files /dev/null and b/5_nn/images/cross_entropy_loss_1.png differ diff --git a/5_nn/images/cross_entropy_loss_2.png b/5_nn/images/cross_entropy_loss_2.png new file mode 100644 index 0000000..cca9b88 Binary files /dev/null and b/5_nn/images/cross_entropy_loss_2.png differ diff --git a/5_nn/images/cross_entropy_loss_sigmod.png b/5_nn/images/cross_entropy_loss_sigmod.png new file mode 100644 index 0000000..f34c004 Binary files /dev/null and b/5_nn/images/cross_entropy_loss_sigmod.png differ diff --git a/6_pytorch/0_basic/1-Tensor-and-Variable.ipynb b/6_pytorch/0_basic/1-Tensor-and-Variable.ipynb index ca92d15..31743ca 100644 --- a/6_pytorch/0_basic/1-Tensor-and-Variable.ipynb +++ b/6_pytorch/0_basic/1-Tensor-and-Variable.ipynb @@ -5,7 +5,14 @@ "metadata": {}, "source": [ "# Tensor and Variable\n", - "这是 PyTorch 基础的第二课,通过本次课程,你能够学会如何像使用 NumPy 一样使用 PyTorch,了解到 PyTorch 中的基本元素 Tensor 和 Variable 及其操作方式。" + "\n", + "PyTorch的简洁设计使得它入门很简单,在深入介绍PyTorch之前,本节将先介绍一些PyTorch的基础知识,使得读者能够对PyTorch有一个大致的了解,并能够用PyTorch搭建一个简单的神经网络。部分内容读者可能暂时不太理解,可先不予以深究,后续的课程将会对此进行深入讲解。\n", + "\n", + "本节内容参考了PyTorch官方教程[^1]并做了相应的增删修改,使得内容更贴合新版本的PyTorch接口,同时也更适合新手快速入门。另外本书需要读者先掌握基础的Numpy使用,其他相关知识推荐读者参考CS231n的教程[^2]。\n", + "\n", + "[^1]: http://pytorch.org/tutorials/beginner/deep_learning_60min_blitz.html\n", + "[^2]: http://cs231n.github.io/python-numpy-tutorial/\n", + "\n" ] }, { @@ -13,7 +20,8 @@ "metadata": {}, "source": [ "## 把 PyTorch 当做 NumPy 用\n", - "PyTorch 的官方介绍是一个拥有强力GPU加速的张量和动态构建网络的库,其主要构件是张量,所以我们可以把 PyTorch 当做 NumPy 来用,PyTorch 的很多操作好 NumPy 都是类似的,但是因为其能够在 GPU 上运行,所以有着比 NumPy 快很多倍的速度。" + "\n", + "PyTorch 的官方介绍是一个拥有强力GPU加速的张量和动态构建网络的库,其主要构件是张量,所以我们可以把 PyTorch 当做 NumPy 来用,PyTorch 的很多操作好 NumPy 都是类似的,但是因为其能够在 GPU 上运行,所以有着比 NumPy 快很多倍的速度。通过本次课程,你能够学会如何像使用 NumPy 一样使用 PyTorch,了解到 PyTorch 中的基本元素 Tensor 和 Variable 及其操作方式。" ] }, {