Fork me on GitHub

Python机器学习笔记:不得不了解的机器学习知识点(2)

      之前一篇笔记: Python机器学习笔记:不得不了解的机器学习知识点(1)

1,什么样的资料集不适合用深度学习?

  • 数据集太小,数据样本不足时,深度学习相对其它机器学习算法,没有明显优势。
  • 数据集没有局部相关特性,目前深度学习表现比较好的领域主要是图像/语音/自然语言处理等领域,这些领域的一个共性是局部相关性。图像中像素组成物体,语音信号中音位组合成单词,文本数据中单词组合成句子,这些特征元素的组合一旦被打乱,表示的含义同时也被改变。对于没有这样的局部相关性的数据集,不适于使用深度学习算法进行处理。举个例子:预测一个人的健康状况,相关的参数会有年龄、职业、收入、家庭状况等各种元素,将这些元素打乱,并不会影响相关的结果。

  其实这里我感觉用下面这张图表示最合适不过了:

 

 

2,softmax函数的数学推导及Python实现

2.1  Softmax函数的原理及其数学推导

  softmax用于多分类过程中最后一层,它将多个神经元的输出,映射到(0, 1)区间内,可以看成概率来理解,从而来进行多分类!

  假设我们有一个数组V,Vi 表示 V 中第 i 个元素,那么这个元素的 Softmax值就是:

  更形象的如下图表示:

  softmax 直白来说就是讲原来输出是 3, 1, -3 通过 softmax 函数一作用,就映射成为(0, 1)的值,而这些值的累和为1,那么我们就可以将其理解成概率,在最后选取输出节点的时候,我们可以选取概率最大的节点,作为我们的预测目标!

2.2 Softmax函数中的损失函数(cross entropy loss)

  说完了Softmax,下面来学习一下 softmax loss。softmax loss如下:

   首先 L 是损失,Sj是Softmax的输出向量S的第 j 个值,表示这个样本属于第 i 个类别的概率。yi前面有个求和符号,j的范围是也是1到类别数T,因此 y 是一个 1*T 的向量,里面的 T个值,而且只有 1个值是1,其他 T-1个值都是0。那么哪个位置的值是1呢?答案是真实标签对应的位置的那个值是1,其他都是0。所以这个公式其实有一个更简单的形式:

  当然此时要限制 j 是指向当前样本的真实标签。

  下面举个例子,假设一个五分类问题,然后一个样本 I 的标签 y = [0, 0, 0, 0, 1, 0],也就是说样本 I 的真实标签是 4,假设模型预测的结果概率(softmax的输出 ) p = [0.1,  0.15,  0.05, 0.6, 0.1],可以看出这个预测是对的,那么对应的损失 L= - log(0.6),也就是说当这个样本经过这样的网络参数产生这样的预测 p 时,它的损失是 -log(0.6)。那么假设 p = [0.15,  0.2,  0.4, 0.1, 0.15],这个预测结果就很离谱了,因为真实标签是 4,而你觉得这个样本是 4的概率只有 0.1 (远不如其他概率高,如果是在测试阶段,那么模型就会预测该样本属于类别3),对应损失 L = - log(0.1)。那么假设 p = [0.05,  0.15,  0.4, 0.3, 0.1],这个预测结果虽然也错了,但是没有前面那么离谱,对应的额损失 L = -log(0.3)。我们知道 log函数在输入小于1的时候是个负数,而 log函数是递增函数所以-log(0.6) < -log(0.3) < -log(0.1)。简单讲就是你预测错比预测对的损失要大,预测错得离谱比预测错得轻微的损失要大

2.3 Softmax的Python实现

  Python代码实现代码如下:

# _*_coding:utf-8_*_
import tensorflow as tf
import numpy as np
import math


# softmax函数,或称归一化指数函数
def softmax(x, axis=1):
    # 为了避免求 exp(x) 出现溢出的情况,一般需要减去最大值
    # 计算每行的最大值
    row_max = x.max(axis=axis)
    # 每行元素都需要减去对应的最大值,否则求exp(x)会溢出,导致INF情况
    row_max = row_max.reshpae(-1, 1)
    x = x - row_max

    x_exp = np.exp(x)
    # 如果是列向量,则axis=0
    x_sum = np.sum(x_exp, axis=1, keepdims=True)
    s = x_exp / x_sum
    return s

# 简单一些
def softmax(x):
    """Compute softmax values for each sets of scores in x."""
    e_x = np.exp(x - np.max(x))
    return e_x / e_x.sum()

# 使用 tf的softmax函数
with tf.Session() as sess:
    tf_s2 = tf.nn.softmax(x, axis=axis)
    s2 = sess.run(tf_s2)

  下面我们分析一下,减去最大值和不减去最大值是否有必要吗?首先看代码:

import numpy as np

def softmax(x):
    """Compute softmax values for each sets of scores in x."""
    e_x = np.exp(x - np.max(x))
    return e_x / e_x.sum()

def softmax1(x):
    """Compute softmax values for each sets of scores in x."""
    return np.exp(x) / np.sum(np.exp(x), axis=0)

scores = [3.0, 1.0, 0.2]
print(softmax(scores))
print(softmax1(scores))
'''
结果输出如下:
[0.8360188  0.11314284 0.05083836]
[0.8360188  0.11314284 0.05083836]
'''

  其实两个结果输出是一样的,即使第一个实现了每列和最大值的差异,然后除以总和,但是问题来了,实现在代码和时间复杂度方面是否相似?哪一个更有效率?

  当然,他们都是正确的,但是从数值稳定性的角度来看,第一个是正确的,因为我们避免了求 exp(x) 出现溢出的情况,这里减去了最大值。我们推导一下:

# 转化公式: a ^(b – c)=(a ^ b)/(a ^ c)

e ^ (x - max(x)) / sum(e^(x - max(x))

= e ^ x / (e ^ max(x) * sum(e ^ x / e ^ max(x)))

= e ^ x / sum(e ^ x)

  

3,欧氏距离和曼哈顿距离

  欧氏距离(也称为欧几里得度量),是应用勾股定理计算两个点之间的直线距离,也就是指m维空间两个点之间的真实距离,或者向量的自然长度(即该点到原点的距离)。

  下面是欧式距离的公式(分别是二维空间,三维空间,n维空间):

   曼哈顿距离表示的是两个点在标准坐标系上绝对轴距之和,是种使用在几何度量空间的几何学用语。

  例如在平面上,坐标(x1, y1)的i点与坐标(x2, y2)的j点的曼哈顿距离为:
 
d(i,j)=|X1-X2|+|Y1-Y2|
 
  如图所示,很直接明了的理解欧氏距离和曼哈顿距离:

  图中红线代表曼哈顿距离,绿色代表欧氏距离,也就是直线距离,而蓝色和黄色代表等价的曼哈顿距离

  欧氏距离和曼哈顿距离的Python实现:

import numpy as np

def manhattan_distance(vec1, vec2):
    """
    This method implements the manhattan distance metric
    :param p_vec: vector one
    :param q_vec: vector two
    :return: the manhattan distance between vector one and two
    """
    return np.sum(np.fabs(vec1 - vec2))

def edclidean_distance(vec1, vec2):
    """
    This method implements the edclidean distance metric
    :param vec1: vector one
    :param vec2: vector two
    :return: the edclidean distance between vector one and two
    """
    # 方法一
    distance = np.sqrt(np.sum(np.square(vec1 - vec2)))

    # method 2
    dist = np.linalg.norm(vec1 - vec2)
    return distance

  

4,什么是数据埋点

  数据埋点我们可以分为两类,其一是页面统计,其二是行为统计。

  页面统计可以帮我们知晓某个页面被多少人访问了多少次,行为统计是指用户在界面上的操作行为,应用最多的是按钮的点击次数。

 

5,请简要说说一个完整的机器学习项目流程

5.1:抽象成数学问题

  明确问题是进行机器学习的第一步。机器学习的训练过程通常都是一件非常耗时的事情,胡乱尝试时间成本是非常高的。
  这里的抽象成数学问题,指的我们明确我们可以获得什么样的数据,目标是一个分类还是回归或者是聚类的问题,如果都不是的话,如果划归为其中的某类问题。

5.2:获取数据

  数据决定了机器学习结果的上限,而算法只是尽可能逼近这个上限。数据要有代表性,否则必然会过拟合。而且对于分类问题,数据偏斜不能过于严重,不同类别的数据数量不要有数个数量级的差距。

  而且还要对数据的量级有一个评估,多少个样本,多少个特征,可以估算出其对内存的消耗程度,判断训练过程中内存是否能够放得下。如果放不下就得考虑改进算法或者使用一些降维的技巧了。如果数据量实在太大,那就要考虑分布式了。

5.3 特征预处理与特征选择

  良好的数据要能够提取出良好的特征才能真正发挥效力。

  特征预处理、数据清洗是很关键的步骤,往往能够使得算法的效果和性能得到显著提高。归一化、离散化、因子化、缺失值处理、去除共线性等,数据挖掘过程中很多时间就花在它们上面。这些工作简单可复制,收益稳定可预期,是机器学习的基础必备步骤。
  筛选出显著特征、摒弃非显著特征,需要机器学习工程师反复理解业务。这对很多结果有决定性的影响。特征选择好了,非常简单的算法也能得出良好、稳定的结果。这需要运用特征有效性分析的相关技术,如相关系数、卡方检验、平均互信息、条件熵、后验概率、逻辑回归权重等方法。

5.4:训练模型与调优

  直到这一步才用到我们上面说的算法进行训练。现在很多算法都能够封装成黑盒供人使用。但是真正考验水平的是调整这些算法的(超)参数,使得结果变得更加优良。这需要我们对算法的原理有深入的理解。理解越深入,就越能发现问题的症结,提出良好的调优方案。

5.5:模型诊断

  如何确定模型调优的方向与思路呢?这就需要对模型进行诊断的技术。
  过拟合、欠拟合判断是模型诊断中至关重要的一步。常见的方法如交叉验证,绘制学习曲线等。过拟合的基本调优思路是增加数据量,降低模型复杂度。欠拟合的基本调优思路是提高特征数量和质量,增加模型复杂度。
  误差分析 也是机器学习至关重要的步骤。通过观察误差样本,全面分析误差产生误差的原因:是参数的问题还是算法选择的问题,是特征的问题还是数据本身的问题……
  诊断后的模型需要进行调优,调优后的新模型需要重新进行诊断,这是一个反复迭代不断逼近的过程,需要不断地尝试, 进而达到最优状态。

5.6:模型融合

  一般来说,模型融合后都能使得效果有一定提升。而且效果很好。
  工程上,主要提升算法准确度的方法是分别在模型的前端(特征清洗和预处理,不同的采样模式)与后端(模型融合)上下功夫。因为他们比较标准可复制,效果比较稳定。而直接调参的工作不会很多,毕竟大量数据训练起来太慢了,而且效果难以保证。

5.7:上线运行

  这一部分内容主要跟工程实现的相关性比较大。工程上是结果导向,模型在线上运行的效果直接决定模型的成败。 不单纯包括其准确程度、误差等情况,还包括其运行的速度(时间复杂度)、资源消耗程度(空间复杂度)、稳定性是否可接受。
  这些工作流程主要是工程实践上总结出的一些经验。并不是每个项目都包含完整的一个流程。这里的部分只是一个指导性的说明,只有大家自己多实践,多积累项目经验,才会有自己更深刻的认识。

 

6,全连接神经网络网络结构

  (此题参考:https://blog.csdn.net/cuiyuan605/article/details/84307323)

  神经网络算法,是使用计算机模拟生物神经系统,来模拟人类思维方式的算法。它的基本单位就是人工神经元。通过相互连接形成一张神经网络。对于神经网络有些了解的盆友可能都知道,神经网络其实就是一个输入 X(向量) 到输出 Y(向量)的映射函数:f(x) = Y,函数的系数就是我们所要训练的网络参数 W,只要函数系数确定下来,对于任何输入xi,我们就能得到一个与之对应的输出 yi,至于 yi 是否符合我们的预期,这就是输入如何提高模型性能方面的问题。

   生物神经网络中,每个神经元与其他神经元连接,当它“激活”时,会传递化学物质到相连的神经元,改变其他神经元的电位,当电位达到一定“阈值”,那么这个神经元也会被激活。

  单个人工神经元的计算公式如下:

  其中:

   为输入参数向量,表示其他神经元输入的信号。

  为每个输入参数的权重值,表示对应神经元信号的权重。

  theta 为阈值或者偏差值,是指该激活神经元的难易程度。

  y 为神经元的输出值,表示该神经元是否被激活。

  Act() 为激活函数,理想的激活函数如下图(a)中的跃阶函数,“1” 为神经元兴奋,“0”为神经元抑制,但由于跃阶函数具有不是连续可导等不好的性质,因此一般采用下面(b) 图的 Sigmoid 函数作为激活函数:

  下面定义一个全连接神经网络:

  全连接神经网络,就是指每一层的每个神经元都和下一层的每个神经元项连接。

  Layer:0 为输入层

  Layer:L 为输出层

  其他L-1 个Layer 为隐层

  输入 x  :,我们称一个输入值 x 为一个样本

  输出 y  :,变量的上标(L)表示该变量出于神经网络的那一层。

   表示第 L 层编号为 i 的神经元

   表示第 L 层的神经元数量

 

 7,全连接神经网络的前向传播

  前向传播比较简单,就是向量点乘,也就是加权求和,然后经过一个激活函数。也就是网络如何根据输入 X 得到输出 Y的。

  记 为第 l-1 层第 k个神经元到第 l 层第 j 个神经元的权重, 为第 l 层 第 j 个神经元的偏置,为第 l 层第 j 个神经元的激活值(激活函数的输出)。不难看出 的值取决于上一层神经元的激活:

  将上面重写为矩阵形式:

  为了方便表示,记  为每一层权重输入,矩阵形式则变为

  利用矩阵形式可以一层层计算网络的激活值,最终能根据输入X 得到相应的输出 

 

8,随机梯度下降法

(此题参考:https://blog.csdn.net/qq_38150441/article/details/80533891 和 https://blog.csdn.net/qq_39037383/article/details/89156894)

  梯度下降算法的思想就是根据人类在渐进学习中,不断从错误中纠正自己的认知的过程中感触到的。

8.1 梯度下降

  简单来说,梯度下降就是从山顶找一条最短的路走到山底最低的地方。但是因为选择方向的原因,我们找到的最低点可能不是真正的最低点。如图所示,黑色标注的路线所指的方向并不是真正的地方。(因为梯度下降是一种思想,没有严格的定义,所以用一个比喻来解释什么是梯度下降)

  既然是选择一个方向下山,那么这个方向该如何选?每次该怎么走?

  先说选的方向,在算法中是以随机方式给出的,这也是造成有时候走不到真正最低点的原因。如果选定了方向,以后每走一步,都选择的时最陡的方向,直到最低点。总结起来就是:随机选择一个方向,然后每次都选择最陡的方向,直到这个方向上能达到的最低点。

  在机器学习算法中,有时候需要对原始的模型构建损失函数,然后通过优化算法对损失函数进行优化,以便寻找到最优的参数,使得损失函数的值最小。而求解机器学习参数的优化算法中,使用最多的就是基于梯度下降的优化算法(Gradient Descent GD)。

  梯度下降的优缺点

  • 优点:效率。在梯度下降法的求解过程中,只需求解损失函数的一阶导数,计算的代价比较小,可以在很多大规模数据集上应用。
  • 缺点:求解的是局部最优值,即由于方向选择的问题,得到的结果不一定是全局最优步长选择,过小使得函数收敛速度慢,过大又容易找不到最优解。

8.2 随机梯度下降

  随机梯度下降(SGD)是一种简单但非常有效地方法,多用于支持向量机,逻辑回归等凸损失函数下的线性分类器的学习。并且SGD已经成功应用于文本分类和自然语言处理中经常遇到的大规模和稀疏机器学习问题。SGD 既可以用于分类计算,也可以用于回归计算。

  随机梯度下降法不是对每个样本集进行求梯度更新参数,而是对一个或者多个样本进行求梯度,更新参数,采集多个样本为样本集再进行如下操作:

1.初始化参数为任意值(可以取到面上任意一点)

2.对样本集里每个样本进行遍历如下操作
      1.求解梯度值

      2.更新参数
   
3.若达到指定迭代次数或者收敛条件,则训练结束

  随机梯度下降法不同于批量梯度下降,随机梯度下降是每次迭代使用一个样本来对参数进行更新。使得训练速度加快。

  对于一个样本的目标函数为:

  对目标函数求偏导:

  参数更新:

   随机梯度下降的优缺点:

  • 优点:由于不是在全部训练数据上的损失函数,而是在每轮迭代中,随机优化某一条训练数据上损失函数,这样每一轮参数的更新速度大大加快。
  • 缺点:准确度下降,由于即使在目标函数为强凸函数的情况下,SGD仍旧无法做到线性收敛。可能会收敛到局部最优,而单个样本并不能代表全体样本的趋势,而且不易于并行实现。

 

9,梯度消失和梯度爆炸的问题是如何产生的?如何解决?

  (参考文献:https://blog.csdn.net/junjun150013652/article/details/81274958   https://blog.csdn.net/qq_25737169/article/details/78847691?utm_medium=distribute.pc_relevant.none-task-blog-BlogCommendFromBaidu-2&depth_1-utm_source=distribute.pc_relevant.none-task-blog-BlogCommendFromBaidu-2)

9.1 为什么要使用梯度更新规则

  在学习梯度消失以及梯度爆炸之前,先简单的了解一下梯度消失的根源——深度神经网络和反向传播。目前深度学习方法中,深度神经网络的发展早就了我们可以构建更深层的网络完成更复杂的任务,深层网络比如深度卷积网络,LSTM等等,而且最终结果表明,在处理复杂任务上,深度网络比浅层的网络具有更好的效果。但是,目前优化神经网络的方法都是基于反向传播的思想,即根据损失函数计算的误差通过梯度反向传播的方式,指导深度网络权值的更新优化。这样做是有一定原因的,首先,深层网络由许多非线性层堆叠而来,每一层非线性层都可以视为是一个非线性函数 f(x)(非线性函数来自于非线性激活函数),因此整个深度网络可以视为一个复合的非线性多元函数

   我们最终的目的就是希望这个多元函数可以很好地完成输入到输出之间的映射,假设不同的输入,输出的最优解是 g(x),那么,优化深度网络就是为了寻找到合适的权值,满足下面公式取得极小值点:

比如最简单的损失函数:

   假设损失函数的数据空间是下图这样的,我们最优的权值就是为了寻找下图中的最小值点,对于这种数学寻找最小值问题,采用梯度下降的方法再适合不过了。

 

9.2 梯度消失,爆炸

  梯度消失与梯度爆炸其实是一种情况,两种情况下梯度消失经常出现,一是在深层网络中,二是采用了不合适的损失函数,比如Sigmoid。梯度爆炸一般出现在深层网络和权值初始化值太大的情况下。

  由于反向传播过程中,前面网络权重的偏导数的计算时逐渐从后往前累乘的,如果使用 σ,tanh 激活函数的话,由于导数小于1,因此累乘会逐渐变小,导致梯度消失,前面的网络层权重更新变慢;如果权重 w 本身比较大,累乘就会导致 前面网络的参数偏导数变大,产生数值上溢。

  因为 sigmoid 导数最大为 1/4 ,故只有当 abs(w) > 4时才可能出现梯度爆炸,因此最普遍发生的问题是梯度消失问题。

解决的方法通常包括:

  • 1,使用ReLU等激活函数,梯度只会为 0或者1,每层的网络都可以得到相同的更新速度
  • 2,采用 LSTM
  • 3,进行梯度裁剪(clip),如果梯度值大于某个阈值,我们就进行梯度裁剪,限制在一个范围内
  • 4,使用正则化,这样会限制参数 w 的大小,从而防止梯度爆炸
  • 5,设计网络层数更少的网络进行模型训练
  • 6,batch  normalization 批量规范化

  举个例子,当我们需要解决一个非常复杂的问题,例如在高分辨率图像中检测数百种类型的对象,我们可能需要训练一个非常深的DNN,可能需要几十层或者上百层,每层包含数百个神经元,通过成千上万个连接进行连接,我们会遇到下面问题。

  • 1,梯度消失或梯度爆炸
  • 2,训练缓慢
  • 3,训练参数大于训练集的风险

  梯度消失的原因:生物神经元似乎是用 Sigmoid(S型)激活函数活动的,因此研究人员在很长一段时间内坚持Sigmoid函数。但是事实证明,ReLU激活函数通常在ANN工作得更好。

  当神经网络有很多层,每个隐藏层都使用Sigmoid函数作为激励函数时,很容易引起梯度消失的问题。我们知道Sigmoid函数有一个缺点:当x较大或较小时,导数接近0,并且Sigmoid函数导数的最大值是 0.25

   下面将问题简单化来说明梯度消失问题,假设输入只有一个特征,没有偏置单元,每层只有一个神经元。

   我们先进行前向传播,这里将 Sigmoid激励函数 写为 s(x):

z1 = w1*x

a1 = s(z1)

z2 = w2*a1

a2 = s(z2)

...

zn = wn*an-1 (这里n-1是下标)

an = s(zn)

  根据链式求导和反向传播,我们很容易得出,其中C是代价函数

   如果我们使用标准方法来初始化网络中的权重,那么会使用一个均值为 0 标准差为 1 的高斯分布。因此所有的权重通常会满足 |wj| < 1,而 s 是小于 0.25 的值,那么当神经网络特别深的时候,梯度呈指数级衰减,导数在每一层至少会被压缩为原来的 1/4 ,当X值绝对值特别大时,导数趋于0,真是因为这两个原因,从输出层不断向输入层反向传播训练时,导数很容易逐渐变为0,使得权重和偏差参数无法被更新,导致神经网络无法被优化,训练永远不会收敛到良好的解决方案,这被称为梯度消失。

  梯度爆炸的原因:当我们将w初始化为一个较大的值时,例如 >10的值,那么我们从输出层到输入层每一层都会有一个 s'(zn)*wn 的增倍,当 s'(zn) 为 0.25时 s'(xn)*wn >2.5,同梯度消失类似,当神经网络很深时,梯度呈指数级增长,最后到输入时,梯度将非常大,我们会得到一个非常大的权重更新,这就是梯度爆炸的问题,在循环神经网络中最为常见。

   总结:从深层网络角度来讲,不同的层学习的速度差异很大,表现为网络中靠近输出的层的学习的情况很好,靠近输入的层学习的很慢,有时甚至训练了很久,前几层的权值和刚开始随机初始化的值差不多。因此,梯度消失,爆炸,其根本原因在于反向传播训练法则,属于先天不足,另外多说一句,Hinton提出capsule的原因就是为了彻底抛弃反向传播。

 

10,LR的原理和Loss的推导

  首先,LR是一个分类模型,讨论二分类情况下,在这个基础上我们假设样本服从伯努利分布(0~1)分布。做了假设分布后下一步就是求分布参数,这个过程一般采用极大似然估计MLE(Maximum Likelihood Estimation),具体的方法就是求该假设分布在训练样本上的联合概率(样本带入连乘),然后求其关于 theta 的最大值,为了方便计算所以一般取 -log,单调性保持不变,所有就有了 logLoss: L(Y, P(Y|X)) = - logP(Y|X)。

 

11,机器学习中,为何要经常对数据做归一化

  (参考文献:https://blog.csdn.net/abc_138/article/details/82798674)

  一般做机器学习应用的时候大部分时间是花费在特征处理上,其中很关键的一步就是对特征数据进行归一化。

  首先要明白归一化的目的是什么,其目的是为了避免数值较大的特征A变化掩盖了数值较小的特征B变化,最终希望让特征AB都能对结果有影响。

  那么为什么要做归一化呢?

  维基百科给出的解释:1,归一化后加快了梯度下降求最优解的速度。2,归一化有可能提高精度。

解释:归一化为什么能提高梯度下降法求解最优解的速度?

  如下图所示(来自:斯坦福机器学习视频)

 

   蓝色的圈圈图代表的是两个特征的等高线。其中左图两个特征 X1和 X2的区间差别非常大,X1区间为[0, 2000] ,x2区间是 [1, 5],像这种有的数据那么大,有的数据那么小,两类之间的幅度相差这么大,其所形成的等高线非常尖。当使用梯度下降法寻求最优解时,很有可能走“之字型”路线(垂直等高线走),从而导致需要迭代很多次才能收敛。而右图对两个原始特征进行了归一化,其对应的等高线显得很圆,在梯度下降进行求解时能较快的收敛,因此如果机器学习模型使用梯度下降法求最优解时,归一化往往非常有必要,否则很难收敛,甚至不能收敛。

解释:归一化有可能提高精度

  一些分类器需要计算样本之间的距离(如欧式距离),例如KNN。如果一个特征值域范围非常大,那么距离计算就主要取决于这个特征,从而与实际情况相悖(比如这时实际情况是值域范围小的特征更重要)。

归一化的类型

1,线性归一化

  这种归一化方法比较适用于在数值比较集中的情况。这种方法有个缺陷,如果max和min 不稳定,很容易使得归一化结果不稳定,使得后续使用效果也不稳定。实际使用中可以用经验常量值来替代 max和 min。

2,标准差标准化

  经过处理的数据符合标准正态分布,即均值为0,标准差为1。

3,非线性归一化

  经常用在数据分化比较大的场景,有些数值很大,有些很小。通过一些数学函数,将原始值进行映射。该方法包括 log、指数,正切等。需要根据数据分布的情况,决定非线性函数的曲线,比如log(V, 2)还是log(V, 10)等。

 

12,关于机器学习拟合问题

12.1 什么是机器学习过拟合?

  所谓过拟合,就是指模型在训练集上的效果很好,在测试集上的预测效果很差。

12.2 如何避免过拟合问题?

  1,重采样Bootstrap 

  2,L1,L2 正则化

  3,决策树的剪枝操作

  4,交叉验证

12.3 什么是机器学习的欠拟合?

  所谓欠拟合就是模型复杂度低或者数据集太小,对模型数据的拟合程度不高,因此模型在训练集上的效果就不好。

12.4 如何避免欠拟合问题?

  1,增加样本数量

  2,增加样本特征的数量

  3,可以进行特征维度扩展

12.5  算法的误差一般是由那几个方面引起的?

  1,因模型无法表达基本数据的复杂度而造成的偏差(bias)——欠拟合

  2,因模型过度拟合训练集数据而造成的方差(variance)——过拟合

 

13,为什么朴素贝叶斯如此“朴素”?

  贝叶斯算法简单高效,在处理分类问题上,是首先要考虑的方法之一。

  贝叶斯分类是一类分类算法的总称,这类算法均以贝叶斯定理为基础,故统称为贝叶斯分类。公式如下:

   该公式最大的优点就是可以忽略AB 的联合概率直接求其条件概率分布。

  而朴素贝叶斯为什么如此朴素,因为他假定所有的特征在数据集中的作用是同样重要和独立的。正如我们所知,这个假设在现实世界中是很不真实的,因此说朴素贝叶斯真的很“朴素”。

  朴素贝叶斯分类是一种非常简单的分类算法,其思想是朴素的。即:对于给出的待分类项,求解在此项出现的条件下各个类别出现的概率,那个最大,就认为此待分类项属于那个类别。

  理论上,朴素贝叶斯模型与其他分类方法相比具有最小的误差率。但是实际上并非总是如此,这是因为朴素贝叶斯模型给定输出类别的情况下,假设属性之间相互独立,这个假设在实际应用中往往是不成立的,在属性个数比较多或者属性之间相关性较大时,分类效果不好。而在属性相关性较小的时,朴素贝叶斯性能最为良好。对于这一点,有半朴素贝叶斯之类的算法通过考虑部分关联性适度改进。

 

14,反向传播算法(BP算法)的推导及其Python实现

  下面学习如何调整一个神经网络的参数,也就是误差反向传播算法(BP算法)。以得到一个能够根据输入,预测正确输出的模型。

14.1,首先我们要了解优化的目标

  根据人工神经元的定义,有以下三个公式:

  其中,Act() 是激活函数,之前学习过。

  根据上面两个公式,可以得出各个神经元之间的通用公式,如下:

  其中上式是人工神经网络正向传播的核心公式。

  那么,我们根据什么来调整神经网络的参数,以得到一个能够正确预测结果的模型呢?请看下面的公式:

  上式用来计算我们期望的输出和实际输出的“差别”,其中cost() 叫做损失函数。我们的期望是损失函数值达到最小。

  但是只根据一次输出的损失值,对参数进行调整,无法使模型适应所有输入样本。我们需要的是,调整参数,使得所有输入样本,得到输出的总损失值最小,而不是只让妻子一个样本的损失值最小,导致其他样本损失值增大。因此有下面公式:

  上式表示一个 batch 的所有样本输出的总损失值的平均值。其中,bn 表示一个 batch中样本的数量。

  为什么不用所有的样本计算损失值,而将所有样本分成一个个的 batch呢?因为所有的训练样本数量太大了,可能有数以百万计,将所有的样本损失值都一起进行运算,计算量过于庞大,大大降低了模型计算的速度。

  而计算总的损失值 C,其中是一个以所有的连接权重 W 和 所有的阈值 theta 未为变量的多元函数。我们想要的模型就是求得 C 最小时,所有 W 和 theta 的值。直接计算显然是不可能的,因为对于一个大的深度神经网络,所有的参数变量,可能数以万计。

  在这里我们使用梯度下降算法来逐步逼近 C的最小值,也即是先随机得到一组参数变量的值,然后计算参数变量当前的梯度,向梯度的反方向,也就是C变小最快的方向,逐步调整参数值,最终得到 C 的最小值,或者近似最小值。

  而将所有样本,随机分成一个个固定长度的 batch,以得到近似的梯度方向,叫做随机梯度下降算法。

14.2 开始求梯度

   那么根据梯度的定义,接下来的任务,就是求取各个参数变量相对于 C 的偏导数。我们将使用误差反向传播算法来求取各个参数变量的偏导数。

  求取偏导数的方法和神经网络正向传播(根据样本计算输出值)的方式类似,也是逐层求解,只是方向正好相反,从最后一层开始,逐层向前。

  首先,我们先求神经网络最后一层,也即是输出层的相关参数的偏导数。为了降低推导的复杂性,我们只计算相对一个样本的损失值函数 Cbi 的偏导数,因为相对于总损失值函数 C 的偏导数值,也不过是把某个参数的所有相对于 Cbi 偏导数值加起来而已。

  根据上面公式,以及 复合函数求导法则,可以得到输出层(L层)某个神经元的权值参数 W 的偏导数,计算公式如下:

  根据前面三个公式求导如下:

  将这三个公式代入上面公式,可以得到:

  我们令:

  则:

  将上式代入损失函数求导的公式中可以得到:

  这样我们就得到了输出层 L 相关的权重参数 W 的偏导数计算公式!

  接下来,同理可以求得输出层 L 相关的阈值 theta 的偏导数计算公式为:

  而根据第二个公式可以得到:

  将上式代入到上上式可以得到:

  这就是 输出层 L 相关的阈值 theta 的偏导数计算公式!

14.3 根据 L 层,求前一层参数的偏导函数

  从下面公式,可知,一个权重参数 W 只影响一个 L-1 层的神经元:

   因此可以得到有下面公式:

 

  将上式代入到上上式可以得到:

  根据假设:

  我们可以得到:

  将上式代入到上上式,可以得到:

  同理,我们可以得到:

  根据14.3 第一个公式可以得到:

  将上式代入到上上式,可以得到:

  这样我们就得到了 L-1 层神经元相关参数的计算公式。

  下面我们还需要推导一下 之间的关系,根据下面公式:

  我们可以得到:

  同理可得:

  将上式代入到上上式,可以得:

  我们知道,一个权重参数 W 只影响一个 L-1 层的神经元,但这个 L-1 层神经元影响了所有 L层的神经元。因此,根据多元复合函数求导法则。有:

  根据我们之前的假设,可以得到:

  将上式代入到上上式,可以得到:

  我们可以知道:

  将上式代入到上上式,可以得到:

  最后将上式代入之前的公式,可以得到:

  这样我们就得到了反向传播,逐层推导的通用公式:

  这里, W 和 Z 都是整箱传播过程中已经算好的常数,而 可以从 L层开始逐层向前推导,直到第1层,第0层是输入层,不需要调整参数,而第L层的参数可以参考下面公式:

 

   下面是全连接神经网络的Python实现代码:

#coding=utf-8
import numpy as np
import matplotlib.pylab as plt
import random
 
class NeuralNetwork(object):
    def __init__(self, sizes, act, act_derivative, cost_derivative):
        #sizes表示神经网络各层的神经元个数,第一层为输入层,最后一层为输出层
        #act为神经元的激活函数
        #act_derivative为激活函数的导数
        #cost_derivative为损失函数的导数
        self.num_layers = len(sizes)
        self.sizes = sizes
        self.biases = [np.random.randn(nueron_num, 1) for nueron_num in sizes[1:]]
        self.weights = [np.random.randn(next_layer_nueron_num, nueron_num)
            for nueron_num, next_layer_nueron_num in zip(sizes[:-1], sizes[1:])]
        self.act=act
        self.act_derivative=act_derivative
        self.cost_derivative=cost_derivative
 
    #前向反馈(正向传播)
    def feedforward(self, a):
        #逐层计算神经元的激活值,公式(4)
        for b, w in zip(self.biases, self.weights):
            a = self.act(np.dot(w, a)+b)
        return a
 
    #随机梯度下降算法
    def SGD(self, training_data, epochs, batch_size, learning_rate):
        #将训练样本training_data随机分为若干个长度为batch_size的batch
        #使用各个batch的数据不断调整参数,学习率为learning_rate
        #迭代epochs次
        n = len(training_data)
        for j in range(epochs):
            random.shuffle(training_data)
            batches = [training_data[k:k+batch_size] for k in range(0, n, batch_size)]
            for batch in batches:
                self.update_batch(batch, learning_rate)
            print("Epoch {0} complete".format(j))
 
    def update_batch(self, batch, learning_rate):
        #根据一个batch中的训练样本,调整各个参数值
        nabla_b = [np.zeros(b.shape) for b in self.biases]
        nabla_w = [np.zeros(w.shape) for w in self.weights]
        for x, y in batch:
            delta_nabla_b, delta_nabla_w = self.backprop(x, y)
            nabla_b = [nb+dnb for nb, dnb in zip(nabla_b, delta_nabla_b)]
            nabla_w = [nw+dnw for nw, dnw in zip(nabla_w, delta_nabla_w)]
        #计算梯度,并调整各个参数值
        self.weights = [w-(learning_rate/len(batch))*nw for w, nw in zip(self.weights, nabla_w)]
        self.biases = [b-(learning_rate/len(batch))*nb for b, nb in zip(self.biases, nabla_b)]
 
    #反向传播
    def backprop(self, x, y):
        #保存b和w的偏导数值
        nabla_b = [np.zeros(b.shape) for b in self.biases]
        nabla_w = [np.zeros(w.shape) for w in self.weights]
        #正向传播
        activation = x
        #保存每一层神经元的激活值
        activations = [x]
        #保存每一层神经元的z值
        zs = []
        for b, w in zip(self.biases, self.weights):
            z = np.dot(w, activation)+b
            zs.append(z)
            activation = self.act(z)
            activations.append(activation)
        #反向传播得到各个参数的偏导数值
        #公式(13)
        d = self.cost_derivative(activations[-1], y) * self.act_derivative(zs[-1])
        #公式(17)
        nabla_b[-1] = d
        #公式(14)
        nabla_w[-1] = np.dot(d, activations[-2].transpose())
        #反向逐层计算
        for l in range(2, self.num_layers):
            z = zs[-l]
            sp = self.act_derivative(z)
            #公式(36),反向逐层求参数偏导
            d = np.dot(self.weights[-l+1].transpose(), d) * sp
            #公式(38)
            nabla_b[-l] = d
            #公式(37)
            nabla_w[-l] = np.dot(d, activations[-l-1].transpose())
        return (nabla_b, nabla_w)
 
#距离函数的偏导数
def distance_derivative(output_activations, y):
    #损失函数的偏导数
    return 2*(output_activations-y)
 
# sigmoid函数
def sigmoid(z):
    return 1.0/(1.0+np.exp(-z))
 
# sigmoid函数的导数
def sigmoid_derivative(z):
    return sigmoid(z)*(1-sigmoid(z))
 
if __name__ == "__main__":
    #创建一个5层的全连接神经网络,每层的神经元个数为1,8,5,3,1
    #其中第一层为输入层,最后一层为输出层
    network=NeuralNetwork([1,8,5,3,1],sigmoid,sigmoid_derivative,distance_derivative)
 
    #训练集样本
    x = np.array([np.linspace(-7, 7, 200)]).T
    #训练集结果,由于使用了sigmoid作为激活函数,需保证其结果落在(0,1)区间内
    y = (np.cos(x)+1)/2
 
    #使用随机梯度下降算法(SGD)对模型进行训练
    #迭代5000次;每次随机抽取40个样本作为一个batch;学习率设为0.1
    training_data=[(np.array([x_value]),np.array([y_value])) for x_value,y_value in zip(x,y)]
    network.SGD(training_data,5000,40,0.1)
 
    #测试集样本
    x_test = np.array([np.linspace(-9, 9, 120)])
    #测试集结果
    y_predict = network.feedforward(x_test)
 
    #图示对比训练集和测试集数据
    plt.plot(x,y,'r',x_test.T,y_predict.T,'*')
    plt.show()

  

14.4:通过一个简单的例子来推导反向传播

  我觉得上面推导,对于大部分人来说,是真的繁琐,既有上标,又有下标,非数学系的可能头疼,最近在网上找了一个大神写的,我觉得简单明了,看了就会。特地学习了一遍,如下:

下面举一个例子,代入数值演示反向传播法的过程,假设你有这样一个网络层:

   第一层是输入层,包含两个神经元i1,i2,和截距项 b1;第二层是隐含层,包含两个神经元 h1,  h2 和截距项目 b2,第三层是输出 o1,o2,每条线上标的 wi 是层与层之间连接的权重,激活函数我们默认的是Sigmoid函数。

  现在对其赋上初始值,如下图所示:

  其中,输入数据  i1=0.05,i2=0.10;

     输出数据 o1=0.01,o2=0.99;

     初始权重  w1=0.15,w2=0.20,w3=0.25,w4=0.30;

           w5=0.40,w6=0.45,w7=0.50,w8=0.55

  目标:给出输入数据 i1,i2(0.05 和 0.10),使其输出尽可能与原始输出 o1, o2(0.01, 0.99)接近。

 step1:前向传播

  1,输入层到隐含层

  计算神经元 h1 的输入加权和:

   神经元 h1 的输出 o1 :(此处使用激活函数为Sigmoid函数)

   同理可以计算出神经元 h2 的输出 o2:

  2,隐含层到输出层

  计算输出层神经元 o1 和 o2 的值:

   神经元 o1 到 Out1 的输出(此处使用激活函数为Sigmoid函数):

   同理可以计算出 o2 到 Out2 的输出(此处使用激活函数为Sigmoid函数):

  这样前向传播的过程就结束了,我们得到输出值为[0.75136079 , 0.772928465],与实际值[0.01 , 0.99]相差还很远,现在我们对误差进行反向传播,更新权值,重新计算输出。

 step2:反向传播

  1,计算总误差

  总误差(Square Error)公式:

  但是有两个输出,所以分别计算 o1 和 o2 的误差,总误差为两者之和:

   2,隐含层到输出层的权值更新

  以权重参数 w5 为例,如果我们想知道 w5 对整体误差产生了多少影响,可用整体误差对 w5 求偏导求出(链式法则):

   下图可以更直观的看清楚误差是怎么样反向传播的:

   下面我们来分别计算每个式子的值(即将链式法则的三个乘积分开计算):

  链式法则乘积中的左边:

   链式法则乘积中的中间:

   (有没有发现这一步很熟悉,其实就是对Sigmoid函数求导,导函数我们都知道是什么)

  最后是链式法则乘积中的右边:

   最后三者相乘:

   这样我们就计算出整体误差E(total) 对 w5 的偏导值。

  回头再看上面的公式,我们发现:

   为了表达方便,我们使用 sigma o1 来表示输出层的误差:

   因此,整体误差 E(total) 对 w5 的偏导公式可以写成:

   如果输出层误差记为负的话,也可以写成:

   最后我们来更新 w5的值:

   (其中,η 是学习速率,这里我们取 0.5)

  同理,我们可以更新 w6, w7,  w8

 

   3,隐含层到隐含层的权值更新

  方法其实与上面更新的差不多,但是由一个地方需要变一下,在上文计算总误差对 w5 的偏导时,是从 out(o1) 到 net(o1) 到 w5 ,但是在隐含层之间的权值更新时,是 out(h1)  到 net(h1) 到 w1,而 out(h1) 会接受 E(o1)  和 E(o2) 两个地方传来的误差,所以这个地方两个都要计算。

   由上图公式,我们可以看到其链式法则的分解, 我们首先计算 Etotal 对 outh1 的偏导::

  而:

   同理,可以计算出:

   两者相加得到总值:

   再计算 outh1 对 net h1的偏导:

   最后计算 neth1 对 w1 的偏导:

   最后三者相乘:

   为了简化公式,用 Sigma(h1) 表示隐含层单元 h1 的误差:

   最后更新 w1的权值:

   同理,可更新 w2,  w3 ,  w4 的权值:

  这样误差反向传播法就完成了,最后我们再把更新的权值重新计算,不停地迭代,在这个例子中第一次迭代之后,总误差E(total)由 0.298371109 下降至 0.291027924 。迭代10000次后,总误差为 0.000035085,输出为[0.015912196, 0.984065734] (原输入为[0.01, 0.99]),证明效果还是不错的。

(参考文献:https://blog.csdn.net/weixin_38347387/article/details/82936585)

  最后我们可以总结出神经网络训练过程,主要分为前向传播和反向传播两个过程,如下所示:

 

posted @ 2019-08-31 11:37  战争热诚  阅读(3021)  评论(2编辑  收藏  举报