什么是神经网络

神经网络是一种模拟人脑的神经网络以期能够实现类人工智能的机器学习技术。顾名思义,神经网络的灵感来自于人体大脑结构,是对大脑中信号传递的一种模拟.

人脑是由神经元组成,成人的大脑中估计有1000亿个神经元之多。这些神经元通过神经突出相连,神经元从一个突出接收生物电信号,经过处理后再传递给其他的神经元.

人类科学家模拟人的大脑希望用非常多的节点的相互连接来实现智能.下图是神经网络的示意图.

对于人类来说很容易辨认出下面这幅图是数字9,但是如果让一个程序员写段程序来辨认图像中的数字,这似乎比登天还难了.

因为每个人写出的9都是不一样的,甚至同一个人每次写的9都是不一样的.

要让计算机能分辨出这些不同的图片都是9,这就需要一定的智能了.

那么神经网络是如何分辨出图片中的数字的呢?
神经网络在最初的时候什么都不会,分辨的很不准,我们要教他学会东西,用大量已知的数据去培训他,网络中有很多参数,神经网络通过调整这些参数来达到认识数字的目的.
我们训练时会拿已经标记好的图片,比如说拿一个9的图片,然后标记这张图片是9,拿神经网络去推断这张图片中的数字,如果推断的有偏差,就根据正确的结果去调整网络中的参数,第二次再拿一张数字为2的图片,让神经网络来分别,再调整参数…如此预测->判断偏差->调整参数的过程重复多次,最终你会神奇的发现,神经网络已经可以准确的判断出图片中的数字了,更可怕的是现在最先进的神经网络判断数字比人类还要准.
所以说神经网络也是要有一个学习过程的,这跟人类很类似,而且的越多,神经网络的智力越高.

从神经网络的示意图可以看到神经网络是有很多层的,我们来大胆的假设假设一下神经网络识别出数字9的过程:
当我们把图片输入到神经网络中,假设第一层可以识别出上半部分是个圆形,假设第二层可以识别出下半部分是竖线,第三层可以将这个圆和竖线组合起来,那么神经网络就可以识别出这张图片是9.

这个假设是正确的吗?让我们在接下来的学习过程中寻找答案吧.

线性分类

在大学里,学生每周的学习时间和期末考试成绩有一定的相关性.
我们调查了一些同学每周的学习时间和期末考试成绩,画成如下这幅图,图中每一个点代表一名同学.

有一名同学我们只调查了他每周学习4小时,那么我们能不能根据这幅图预测一下这名同学大概能考多少分呢?
虽然我们不能准确的预测出这位同学的分数,但是他有很大概率他考200分左右

再来看一个例子,企业hr招聘的时候看重两方面,一方面是人际交往能力,另一方面是专业技能,图中红色的是未被录用的,蓝色的是被某知名企业录用的

如果有一个学生专业能力60分,交际能力70分,那么他会被录用吗?这种预测如何做?
我们可以在录用和未录用同学之间画一条直线,做预测的时候就可以看这位同学是在直线上方还是在直线下方,直线上方的同学大概率会被录用,而在这条直线下面的同学大概率不会被录用

所以这个问题的关键是如何找出这条线.对么?那么这条线如何找出来呢?
假设这条线为

y=Wx+b

我们可以先随机赋予W和b的值

然后将已知点逐个带入方程调整参数,比如说带入一个蓝色的点,发现直线的左边应该往下一调整一下,调整参数W和b,让直线的左边向下一点

在带入一个红色的点,发现右边应该往上一调整,再调整参数W和b

重复这个动作将所有已知的点都带入方程,多次调整参数后,直到大部分的红点都在直线下面,大部分蓝点都在直线上面,我们就找到这条直线了.

这个调整参数的过程就是模型的训练过程,神经网络也是用类似的方式进行训练的.
当这个线性模型被训练好后,这个模型就具具备了预测同学是否被录用的智慧.

还有一种方法是计算每个点到直线的距离,如果所有蓝点和所有红点到直线的距离都是最小的,也可以找出这条直线.

非线性分类

还是企业hr招聘的例子,在很多情况仅仅通过一条直线很难区分两个类别,就像这样

像这样不能通过一条直线来划分两个类的,我们可以通过两条直线,来区分这些类别,最后将这两条直线融合起来,就像这样

如果用神经元来表示就是这样

将两个线性方程叠加,最终得到一个非线性方程

神经元

神经网络中的神经元和线性分类很类似,一个神经元的输入值,需要先乘一个参数W,再加上一个偏执b,会得到输出值

所以神经网络中的每一个神经元本质上就是一个线性分类器,而神经网络就是由许许多多的线性分类组成的.

y = Wx+b

上一节中,仅仅一个神经元就能够拥有预测同学是否被录用的智慧,那么当神经网络中的神经元多到和人体大脑中的神经元一样多,几千亿个,那么神经网络能不能通过学习实现真正的智能呢?(因为现今计算机的计算速度和内存的限制,神经网络中的神经元数量还远远达不到人脑神经元的水平)

ReLU

我们来回顾一下上一节的内容,当一条线性方程不足以区分现有的类别的时候,我们可以造两个线性方程,然后将两个线性方程叠加,最终得到一个非线性方程.

这时候你可能会说了,我读书少,你不要骗我,两个线性方程叠加会是非线性方程??

y = W2(W1x+b1)+b2    =>    y = W2W1x+b1W2+b2

这不还是线性方程么?
的确,为了让多个线性方程叠加可以实现非线性方程的效果,第一个神经元的结果没有直接进入第二个神经元而是经过了一个非线性处理.

非线性处理的方法有很多种,我们介绍一个最简单的办法ReLU

如图所示,这是一个最简单的非线性方程
x>0的时候y=x,当x<0的时候y=0

第一个神经元的结果先通过ReLU变成非线性方程,然后在和第二个神经元叠加,最终得到非线性方程.

如果你的问题两个神经元叠加都解决不了的话,应该怎么办?比如下面这幅图

接着加神经元呗,多加几层神经元总可以表示出来.

神经网络就是通过多个简单的线性方程叠加解决复杂的非线性问题的.

多维空间的分类

在二维空间中,我们用一条曲线来区分两个类别

那么在三维空间中呢?自然是用一个曲面来区分两个类别的,用神经网络表示为


以此类推,在N维空间中的分类问题,用神经网络表示出来就是这个样子的

在我们之前的例子中,同学被企业录取和没有被企业录取,只需要分两个类别,但神经网络通常会遇到很多分成多个类别的情况,比如下面这个神经网络结构图,就是识别图片中0~1数字的分类.输入为图片的像素值,输出的10个类别分别代表10种数字.

在多维分类问题中,每个节点的输入值都是前面节点输出值的和

所以有公式

其中的softmax我们后面会讲到,不过这样写非常麻烦,通常我们会用矩阵公式

y = Wx+b

softmax与sigmoid

还来看企业hr招聘的例子

现在来回答这样一个问题,有一个学生专业技能25分,人际交往20分,这名同学被录用的概率是多少?
我们可以通过该同学在直线上面或是直线下面判断是否被企业录用,但是被录用的概率如何计算呢?
通过观察发现,在直线上面的里直线越远,被录用的概率越大,在直线下面的,里直线越远被录用的概率越小

所以我们可以根据离直线的距离去算这个概率

某点的概率就是用该点到直线的距离除以所有点到直线距离之和.

但是问题来了在直线下面的点到直线的距离是负数怎么办?

为了解决这个问题,我们可以用softmax公式计算概率

和softmax相类似功能的函数还有sigmoid,区别是softmax的概率之和为1,而sigmoid各概率相加只和不为1,用这两个公式算出来概率从大到小的顺序是一样的.

sigmoid相当于softmax的退化,只能用于计算两种类别的概率,softmax可以用于计算多种类别的概率.因为sigmoid公式比较简单,被广泛用于线性分类的概率计算.

softmax和sigmoid都有一个相同的特点,会把(-无穷,+无穷)的值压缩到(0,1)之间,值越大就越接近1,值越小就越接近0

现在来思考一个问题,如果我神经网络的每一次线性分类都做一次sigmoid操作,对最终的结果有没有影响?
答案是没有影响,原因如下:

  • 如果我们要的是概率,计算(-无穷,+无穷)的概率和计算(0,1)的概率是一样的
  • 如果我们要的是值也可以通过最后一个线性分类的参数来调整最终的比例
  • 因为用(0,1)之间的数代替(-无穷,+无穷)的数来进行计算显然更容易.

一般来说神经网络通常会在每一个神经元后面加一个sigmoid,在最终输出添加softmax得到每种分类的概率.

one-hot encoding

我们还来看神经网络判断图片中数字的例子,假设现在输入一张图片中数字为4的图片,模型给出的概率,有可能是这样的

那么我们通常取概率最大那个作为结论,所以我们会模型给出的结论是图片中的数字是4.这是模型的推断过程.

在训练过程中,我们会将图片的真实结果进行标记,告诉模型你预测的对不对,如果不对的话要调整参数.

对真实结果标记相当于概率为100%,就像下图这样

下面是对0~10所有类别的标记

这种标记方法叫做one-hot encoding,神经网络通常是用这种方式标记训练数据的.

误差与交叉熵

神经网络中常用两种方法评价一个模型的好坏.

第一种方法是误差

误差就是真实值与预测值的差值

很显然误差的值越小模型越好

第二种方法是交叉熵

交叉熵是一个数学定理,计算方法是将所有点的概率的对数的相反数求和

cross = -ln(p1)-ln(p2).....-ln(pn)

交叉熵有一个特点,交叉熵的值越小模型越好,交叉熵的值越大模型越差

以后的模型训练过程中,我们就通过误差或交叉熵来评估一个模型的好坏,通过不断降低误差或交叉熵来优化训练模型.

梯度下降

神经网络的训练过程相当于不断调整和优化分类线的过程(多维相当于不断调整矩阵W和矩阵b),所以交叉熵可以表示成一个和分类线相关的方程

cross = f(Wx+b)

所以我们可以通过判断cross是不是最小来判断神经网络的模型是不是最优的.
也就是说模型的训练过程就是cross函数寻找最低点的过程.

我们假设交叉熵的函数是一个二次方程,现在的cross为图中的点

我们应该如何调整能让cross变小?
应该往斜率或梯度减小的方向调整,所以这种调整方法叫做梯度下降.

函数某点的梯度,或者说是斜率,就是该点的导数.

多维空间中也是同样道理

反向传播

神经网络中可能会有很多神经元会影响最终corss的大小,那么调整的时候该如何调整?

我们可以把最终的cross看成是多个cross分支相加的结果,我们分别画出每个cross分支的图形

从图中可以看出,当各分支cross叠加后,梯度最大的那个分支对最终的结果影响最大.所以我们可以对每一个神经元进行求梯度,梯度大的说明对cross的结果影响比较大,调整的比例就需要大一些,梯度小的说明对cross影响比较小,调整比例相应也可以小一些.

反向传播

在对所有神经元进行求导的过程中有一个小技巧,比如我们要对如下神经网络进行求导,我们不必每一个神经元都对最终结果y求导.可以先计算c对y的导数,而后计算b对y的导数,而b对y的导数可以转换成c对y的导数乘以b对c的导数.

如此类推,每一个神经元相的导数相当于本身的导数乘以后一个神经元的导数.
这种将复杂函数求导转换成多个线性函数求导的方法叫做链法则.

由于求导是从后向前进行的,故名反向传播.

Hello!TensorFlow

还记得在神经网络原理里面提到过的线性分类的例子么?
在大学里,学生每周的学习时间和期末考试成绩有一定的相关性.
下面是一些同学的数据分布.

我们用tensorflow来训练这样一条直线,达到预测同学分数的效果.

import random

train_features = [random.uniform(0, 100) for i in range(100)]
train_labels = [n * 2 + random.uniform(-20, 20) for n in train_features]

import matplotlib.pyplot as plt

plt.scatter(train_features, train_labels, s=75, alpha=0.5)

import tensorflow as tf

weights = tf.Variable(0.0)
bias = tf.Variable(0.0)

x = tf.placeholder(tf.float32)
y = tf.placeholder(tf.float32)

predict = weights * x + bias

loss = tf.square(y - predict)

sgd = tf.train.GradientDescentOptimizer(0.000005)
train_op = sgd.minimize(loss)

with tf.Session() as sess:
    sess.run(tf.global_variables_initializer())

    for i in range(100):
        train_feature = train_features[i]
        train_label = train_labels[i]

        sess.run(train_op, feed_dict={x: train_feature, y: train_label})
        loss_value = sess.run(loss, feed_dict={x: 1, y: 2})
        p_x = [1, 100]
        p_y = [sess.run(weights) + sess.run(bias), sess.run(weights) * 100 + sess.run(bias)]
        plt.plot(p_x, p_y)
        print("Epoch: %s,loss:%s,weight:%s,bias:%s" % (
            i, loss_value, sess.run(weights), sess.run(bias)))

plt.show()

结果

我们可以把线的变化打印出来,可以看到随着训练的进行,tensorflow逐渐找到了我们想要的线

建立图

在tensorflow中声明变量,定义关系,甚至后面声明优化器什么的,其实tensorflow什么都没有做,只是将这些东西存了起来,在tensorflow的内部形成了一张graph.以后tensorflow所有的计算都是围绕这个graph来展开的.

当tensorflow执行run的时候,才会把存在graph中的东西拿出来做计算.这也是为什么tensorflow总要run一下的原因.

gragh.py

class Graph():
    def __init__(self):
        self.op_list = []

    def add_to_graph(self, op):
        self.op_list.append(op)


graph = Graph()

ops.py

from .graph import graph

class Op():
    def __init__(self):
        self.name = self.__class__.__name__
        self.value = None


class Variable(Op):
    def __init__(self, value):
        super().__init__()
        self.value = value
        graph.add_to_graph(self)


class placeholder(Op):
    def __init__(self, type):
        super().__init__()
        self.type = type
        graph.add_to_graph(self)


class Multiply(Op):
    def __init__(self, input1, input2):
        super().__init__()
        self.input1 = input1
        self.input2 = input2
        graph.add_to_graph(self)


class Add(Op):
    def __init__(self, input1, input2):
        super().__init__()
        self.input1 = input1
        self.input2 = input2
        graph.add_to_graph(self)


class Sub(Op):
    def __init__(self, input1, input2):
        super().__init__()
        self.input1 = input1
        self.input2 = input2
        graph.add_to_graph(self)


class Square(Op):
    def __init__(self, input1):
        super().__init__()
        self.input1 = input1
        graph.add_to_graph(self)


class GradientDescentOptimizer(Op):
    def __init__(self, learn_rate):
        super().__init__()
        self.learn_rate = learn_rate
        graph.add_to_graph(self)


class Minimize(Op):
    def __init__(self, loss):
        super().__init__()
        self.loss = loss
        graph.add_to_graph(self)


参考:

  • https://github.com/tobegit3hub/miniflow

实施正向传播

__init__.py

from . import graph
from . import session
from . import ops
from . import train

int32 = int
float32 = float
float64 = float

global_variables_initializer = train.GlobalVariablesInitializer
Session = session.Session
Variable = ops.Variable
placeholder = ops.Placeholder
square = ops.Square

graph.py

class Graph():
    def __init__(self):
        self.op_list = []

    def add_to_graph(self, op):
        self.op_list.append(op)


graph = Graph()

ops.py

from .graph import graph


class Op():
    def __init__(self):
        self.name = self.__class__.__name__
        self.value = None

    def forward(self):
        raise NotImplementedError

    def __mul__(self, other):
        return Multiple(self, other)

    def __add__(self, other):
        return Add(self, other)

    def __sub__(self, other):
        return Sub(self, other)


class Variable(Op):
    def __init__(self, value):
        super().__init__()
        self.value = value
        graph.add_to_graph(self)

    def forward(self):
        return self.value


class Placeholder(Op):
    def __init__(self, type):
        super().__init__()
        self.type = type
        graph.add_to_graph(self)

    def forward(self):
        return self.value


class Multiple(Op):
    def __init__(self, input1, input2):
        super().__init__()
        self.input1 = input1
        self.input2 = input2
        graph.add_to_graph(self)

    def forward(self):
        self.value = self.input1.value * self.input2.value


class Add(Op):
    def __init__(self, input1, input2):
        super().__init__()
        self.input1 = input1
        self.input2 = input2
        graph.add_to_graph(self)

    def forward(self):
        self.value = self.input1.value + self.input2.value


class Sub(Op):
    def __init__(self, input1, input2):
        super().__init__()
        self.input1 = input1
        self.input2 = input2
        graph.add_to_graph(self)

    def forward(self):
        self.value = self.input1.value - self.input2.value


class Square(Op):
    def __init__(self, input1):
        super().__init__()
        self.input1 = input1
        graph.add_to_graph(self)

    def forward(self):
        self.value = self.input1.value ** 2
        return  self.value

session.py

class Session():
    def __init__(self):
        pass

    def __enter__(self):
        return self

    def __exit__(self, type, value, trace):
        pass

    def run(self, op, feed_dict=None):

        if feed_dict is not None:
            for op_or_opname, value in feed_dict.items():
                op_or_opname.value = value

        return op.forward()

train.py

from .graph import graph

class GradientDescentOptimizer():
    def __init__(self, learn_rate):
        self.learn_rate = learn_rate

    def forward(self):
        pass

    def minimize(self,loss):
        return Minimize(loss)

class Minimize():
    def __init__(self, loss):
        self.loss = loss

    def forward(self):
        for op in graph.op_list:
            op.forward()

class GlobalVariablesInitializer():
    def __init__(self):
        pass

    def forward(self):
        pass

参考:

实施反向传播

__init__.py

from . import graph
from . import session
from . import ops
from . import train

int32 = int
float32 = float
float64 = float

global_variables_initializer = train.GlobalVariablesInitializer
Session = session.Session
Variable = ops.Variable
placeholder = ops.Placeholder
square = ops.Square

graph.ph

class Graph():
    def __init__(self):
        self.op_list = []
        self.gradients = {}

    def add_to_graph(self, op):
        self.op_list.append(op)


graph = Graph()

ops.py

from .graph import graph


class Op():
    def __init__(self):
        self.name = self.__class__.__name__
        self.value = None

    def forward(self):
        raise NotImplementedError

    def backward(self):
        raise NotImplementedError

    def updateValue(self,learning_rate):
        pass

    def __mul__(self, other):
        return Multiple(self, other)

    def __add__(self, other):
        return Add(self, other)

    def __sub__(self, other):
        return Sub(self, other)


class Variable(Op):
    def __init__(self, value):
        super().__init__()
        self.value = value
        graph.add_to_graph(self)

    def forward(self):
        return self.value

    def backward(self):
        pass

    def updateValue(self,learning_rate):
        grad = graph.gradients[self] * learning_rate
        self.value -= grad

class Placeholder(Op):
    def __init__(self, type):
        super().__init__()
        self.type = type
        graph.add_to_graph(self)

    def forward(self):
        return self.value

    def backward(self):
        pass


class Multiple(Op):
    def __init__(self, input1, input2):
        super().__init__()
        self.input1 = input1
        self.input2 = input2
        graph.add_to_graph(self)

    def forward(self):
        self.value = self.input1.value * self.input2.value

    def backward(self):
        graph.gradients[self.input1] = self.input2.value * graph.gradients[self]
        graph.gradients[self.input2] = self.input1.value * graph.gradients[self]


class Add(Op):
    def __init__(self, input1, input2):
        super().__init__()
        self.input1 = input1
        self.input2 = input2
        graph.add_to_graph(self)

    def forward(self):
        self.value = self.input1.value + self.input2.value

    def backward(self):
        graph.gradients[self.input1] = graph.gradients[self]
        graph.gradients[self.input2] = graph.gradients[self]


class Sub(Op):
    def __init__(self, input1, input2):
        super().__init__()
        self.input1 = input1
        self.input2 = input2
        graph.add_to_graph(self)

    def forward(self):
        self.value = self.input1.value - self.input2.value

    def backward(self):
        graph.gradients[self.input1] = graph.gradients[self]
        graph.gradients[self.input2] = -graph.gradients[self]


class Square(Op):
    def __init__(self, input1):
        super().__init__()
        self.input1 = input1
        graph.add_to_graph(self)

    def forward(self):
        self.value = self.input1.value ** 2
        return self.value

    def backward(self):
        graph.gradients[self.input1] = 2 * self.input1.value

session.py

class Session():
    def __init__(self):
        pass

    def __enter__(self):
        return self

    def __exit__(self, type, value, trace):
        pass

    def run(self, op, feed_dict=None):

        if feed_dict is not None:
            for op_or_opname, value in feed_dict.items():
                op_or_opname.value = value

        return op.forward()

train.py

from .graph import graph


class GradientDescentOptimizer():
    def __init__(self, learn_rate):
        self.learn_rate = learn_rate

    def forward(self):
        pass

    def minimize(self, loss):
        return Minimize(loss, self.learn_rate)


class Minimize():
    def __init__(self, loss, learn_rate):
        self.loss = loss
        self.learn_rate = learn_rate

    def forward(self):
        for op in graph.op_list:
            op.forward()

        for op in graph.op_list[::-1]:
            op.backward()
            op.updateValue(self.learn_rate)


class GlobalVariablesInitializer():
    def __init__(self):
        pass

    def forward(self):
        graph.gradients = {i: 0 for i in graph.op_list}

test.py

import miniflow as tf

def main():
    train_features = range(10)
    train_labels = [2 * n + 1 for n in range(10)]

    weights = tf.Variable(0.0)
    bias = tf.Variable(0.0)
    x = tf.placeholder(tf.float32)
    y = tf.placeholder(tf.float32)

    predict = weights * x + bias
    loss = tf.square(y - predict)
    sgd_optimizer = tf.train.GradientDescentOptimizer(0.01)
    train_op = sgd_optimizer.minimize(loss)

    with tf.Session() as sess:
        sess.run(tf.global_variables_initializer())

        for epoch_index in range(10):
            sample_number = len(train_features)
            train_feature = train_features[epoch_index % sample_number]
            train_label = train_labels[epoch_index % sample_number]

            sess.run(train_op, feed_dict={x: train_feature, y: train_label})
            loss_value = sess.run(loss, feed_dict={x: 1.0, y: 3.0})
            print("Epoch: %s, loss: %s, weight: %s, bias: %s" % (
                epoch_index, loss_value, sess.run(weights), sess.run(bias)))

if __name__ == "__main__":
    main()

tensorflow安装

Anaconda

Anaconda 是 Python 的一个发行版,类似Ubuntu和linux的关系.
Anaconda最大的特点是将几乎所有的工具、第三方包都当做package对待,甚至包括python和Anaconda自身,所以Anaconda可以很方便的装多个package,并自如的切换package,这就实现了在多个python环境之间的切换,以及多个python环境共存的问题.Anaconda是解决多个项目使用不同版本的库的好方案.

可以直接去官网下载anaconda官网

创建python环境

我们再来创建一个tensorflow的环境

conda create --name python3 python=3.5

这里要安装3.5或者3.6的版本,TensorFlow supports Python 3.5.x and 3.6.x on Windows.

列出现有的python环境

conda info -e

在各个环境中切换

activate python3 # for Windows
source activate python3 # for Linux & Mac

安装tensorflow

pip install --ignore-installed --upgrade tensorflow

数据预处理2

如果直接用原始数据进行计算的话,数值跨度会很大,这会造成cross函数呈现一个椭圆型,进行计算的时候,有可能按照椭圆型走,收敛速度慢,或者说是不够稳定.

如果将原始数值等比例映射到(0,1)之间,收敛速度会明显更稳定.减小因数值跨度过大造成的浮点数误差等不良后果.

我们还进一步让数据的均值为0,以提高数据的稳定性.

预处理图片

举个预处理图片的例子,图片是由很多像素组成的,每个像素又由RGB三个颜色通道构成,每个颜色通道色值最大为255,最小为0.比如一个像素为(255,255,255)那么这个点为白色,如果(0,0,0)则为黑色.

对于色值的缩放只需要将所有色值除255即可,接着将所有值减0.5即可让均值为0

import cv2
import numpy as np

im = cv2.imread('1.jpg')

print(im.shape)
print(np.min(im),np.max(im))
im = im * 1/255 -0.5
print(np.min(im),np.max(im))

训练集,验证集,测试集

[youku-video:XMzU3ODg3NTk4NA]

测试集

一般情况,我们训练模型的时候会把数据分为两个集合,训练集和测试集.测试集不参与训练,只做模型的评估使用,这是为了防止模型记住数据而不是学会规则,产生过拟合后对模型评估过高.

划分验证集和测试集的方法可以使用scikit-learn模块中的train_test_split函数

train_datas, test_datas = train_test_split(datas, test_size=0.2)

如果你还没有安装可以先安装

pip install -U scikit-learn

or

conda install scikit-learn

验证集

有时候,在模型调试过程中,我们会反复多次调整模型参数,以达到最优效果,由于我们是通过测试集的评估结果来调试模型,会向着评估结果变好的方向调整.这相当于测试集间接参与了训练.

当调试模型次数多次的时候需要建立验证集,验证集不参与模型的训练,用于调试过程中对模型的评估,测试集只用作最终结果的汇报工作.

注意:训练集,验证集,测试集的划分一定要随机划分

交叉验证

如果只做一次分割,对训练集、验证集和测试集的样本数比例,还有分割后数据的分布是否和原始数据集的分布相同等因素比较敏感,不同的划分会得到不同的最优模型,而且分成三个集合后,用于训练的数据更少了。于是就有了交叉验证.

交叉验证在模型训练调试的过程中每次都会随机从训练集中选取数据作为验证集,这样就杜绝了因为数据分布和划分的问题导致会的到不同模型的问题.

    for i in range(EPOCHS):
        X_train, y_train = shuffle(X_train, y_train)
        for offset in range(0, num_examples, BATCH_SIZE):
            end = offset + BATCH_SIZE
            batch_x, batch_y = X_train[offset:end], y_train[offset:end]
            sess.run(training_operation, feed_dict={x: batch_x, y: batch_y})

过拟合

有的时候模型学习的太好了,就像这样

我们不希望模型过于好,因为这样的模型只适用于当前的训练数据,如果拿另外一批数据,模型的性能就会立刻下降,我们希望训练出更泛化的模型,适应所有的数据.这种情况叫做模型过拟合.

下面的模型就比上面的要好一些,因为它没有过分的依赖某些点,更适应大多数的数据.

最简单的防止过拟合的方法是当cross不再显著下降,或开始增长的时候,停止训练

Dropout

在大规模的神经网络中有这样两个缺点:1. 费时;2. 容易过拟合

dropout是指在深度学习网络的训练过程中,按照一定的概率将一部分神经网络单元暂时从网络中丢弃,相当于从原始的网络中找到一个更的网络

对于一个有 N 个节点的神经网络,有了dropout后,就可以看做是 2^N 个模型的集合了,但此时要训练的参数数目却是不变的,这就缓解了费时的问题。

在训练过程中由于丢弃是随机的,所以模型不会依赖任何一个节点,所以过拟合的问题也解决了.


参考:

保存和恢复模型

保存模型

import tensorflow as tf

w1 = tf.Variable(tf.random_normal(shape=[2, 3]))
w2 = tf.Variable(tf.random_normal(shape=[4, 5]))

saver = tf.train.Saver()
sess = tf.Session()
sess.run(tf.global_variables_initializer())

for i in range(1000):
    if i % 100 == 0:
        saver.save(sess, './model/myModel',i)

模型文件

checkpoint文件里面记录了保存的最新的checkpoint文件以及其它checkpoint文件列表。在inference时,可以通过修改这个文件,指定使用哪个model

.meta文件保存的是图结构,我们可以通过tf.train.import_meta_graph('/tmp/model.ckpt.meta')恢复模型

index文件存储了各变量在data文件中对应的偏移值,校验和,以及一些辅助数据.

data文件中保存了模型的所有变量.

恢复模型

checkpoint = tf.train.get_checkpoint_state("logdir")
if checkpoint and checkpoint.model_checkpoint_path:
    saver.restore(sess, checkpoint.model_checkpoint_path)
    print ("Successfully loaded:", checkpoint.model_checkpoint_path)

卷积

我们的图片是由像素的值组成的,而像素是通过数值存储到计算机中的,就像这样

图片的宽度和高度,是指图片在水平方向上像素的大小,和垂直方向像素的大小.每个像素有一个色值在0~255之间.
现在来做一件事,这里有一个3*3的格子,每个格子中都有一个数,我们叫它filter

在图片中画出与filter同样大小的格子,然后依次与filter中对应位置的数值相乘,然后把乘积相加,再除以格子的数量,最后会得到一个数值.

然后把filter向右移动一个格子,重复之前的操作.

直到将整张图片乘完,将得到的数值组合起来回得到一幅新的图片.

这个操作叫做卷积.

通过调整卷积核filter中的数值你还可以得到像下面这几种图片


参考

  • https://mlnotebook.github.io/post/CNN1/

为什么使用卷积

经常有这样的需求,判断一张图片中是否有某物体,比如说下图,我们要区分一张图片中是否有一个人,但是这个人有可能出现在图片的上方,也有可能出现在图片的下方,也有可能是任何位置.

为了解决这类问题,于是就有了卷积网络.卷积网络有两点好处:

  1. 卷积网络中的filters也就是参数W相当于在图片的任何位置都训练过,所以卷积网络可以分别出来图片中任何位置的特征,这个性质叫平移不变性.
  2. 由于图片的各个地方公用了参数fiters,所以使网络中的参数变少了,训练速度加快.

卷积中的参数

输入大小


输入高度就是图片的高度,输入宽度就是图片的宽度.

输出大小


输出高度就是输出数据的高度,输出宽度就是输出数据的宽度.

filter大小


过滤器的高度和宽度.

padding


当过滤器如图中这样开始扫描图片,得到的输出会比原图小一圈,这种填充方法叫做same.

如果不想让输入的尺寸减小,那么可以将边缘填充0,这样卷积输出的尺寸就会和输入尺寸一致.

步长


过滤器每一步走过的距离叫做步长.

厚度


输入数据是有厚度的,因为图片会有RGB三个通道,所以图片的输入厚度为3,同样的filter也有厚度,这样的话过滤器的扫描过程就是矩阵相乘,所以输出数据也是有厚度的.

多层卷积

每次卷积操作我们可以通过增加filter来改变输出的depth的大小

在流行的卷积网络模型中,通常会用多层卷积,每层卷积会减小图片的尺寸,但是会增加图片的厚度

为什么把图片给卷没了??
其实在卷积的过程中,逐步完善的还有一个矩阵,就是filters,也就是W
我们可以理解为卷积就是通过filters将复杂的图片映射成简单的结果的过程,而卷积的训练就是找到最优的filters的过程.
这里有一副VGGNet的卷积图

图片通过卷积后剩余的值就是图片的特征,也就是图片的内容.
我们可以理解图片通过卷积变成了内容分类.
图片->卷积->内容分类

参考:

  • http://cs231n.github.io/convolutional-networks/

池化2

图片通常有区域化特征,同一区域颜色相同或相近,比如一张图片的某个像素是黑色的,这个像素周边的点大概率也是黑色的,如果这个像素是红色的,那么这个像素周边的点大概率也是红色的,那么为什么不能用一个像素来代表这块区域呢?这样既减少了计算量加快计算速度,又能高效的提取图片特征,更能防止过拟合.所以就有了池化.

池化的原理就是将一个区域的值用一个值来表示,下图为一个最大池化的过程,用红色区域里面的最大值代表这个区域.

最常见的池化操作为平均池化mean pooling和最大池化max pooling,最大池化是取区域中的最大值,平均池化是取区域中的平均值.

SGD与学习率

SGD是更新模型参数的一种算法,具体算法为

其中,n是学习率,gt是梯度

学习率是开发人员设置的参数.模型每个神经元的新参数=原参数-学习率*导数.
可以看出,对交叉熵影响越大的参数调整的幅度就会越大.

学习率是每次调整的幅度,学习率越大,模型训练的速度越快.但是学习率过高反而模型的训练效果不是很好.

但是每次调整不能太大,因为每次调整太大的话,可能会错过最低点,导致交叉熵反而增大.

我们训练神经网络的时候,一开始学习率可以大点,最后慢慢减小学习率.


一维2多维

如何实现多层网络

多层网络相当于多个线性模型叠加,所以只需要将

predict = weights * x + bias

修改为

l1 = weights1 * x + bias1
predict = weights2 * l1 + bias2


当然,每个线性模型之间可能需要ReLU和sigmoid

l1 = ReLU(sigmoid(weights1 * x + bias1))
predict = weights2 * l1 + bias2

如何实现多维网络

将一维模型变成多维模型也非常简单

一维模型的输入x和参数W、b都是一个值,而多维模型的输入x和W、b是由多个值组成的矩阵.

所以只需要把代码中的数值改成矩阵,把数值运算改成矩阵运算就实现了一维到多维的转换.

MNIST数据集

MNIST数据集(Mixed National Institute of Standards and Technology database)是美国国家标准与技术研究院收集整理的大型手写数字数据库,包含60,000个示例的训练集以及10,000个示例的测试集.


我们可以在这里下载数据集.
下载后会得到这样四个文件

tensorflow中包含了MNIST数据集的下载代码

from tensorflow.examples.tutorials.mnist import input_data
mnist = input_data.read_data_sets("MNIST_data/", one_hot=True)

得到一个数据集后的首要任务是将数据可视化,从感官上了解数据的具体情况.
我们读取到的是一个namedtuple结构,可以这样访问里面的数据

# 训练集
train_images = mnist.train.images
train_labels = mnist.train.labels

# 验证集
validation_images = mnist.validation.images
validation_labels = mnist.validation.labels

# 测试集
test_images = mnist.test.images
test_labels = mnist.test.labels

打印数据集中的数据个数

print(mnist.train.num_examples)
print(mnist.validation.num_examples)
print(mnist.test.num_examples)

得到结果为

55000
5000
10000

这个数据集中包含55000条训练数据,5000条验证数据,和10000条测试数据

取出一条数据,打印数据的shape

im = mnist.train.images[0]
print(im.shape)

结果为

(784,)

每条数据是一个长度为784的一维矩阵,这是因为数据经过标准化处理了,每条数据为手写图片的784个像素点

把数据打印出来看看

打印数据的最大值,最小值

print(np.min(im),np.max(im))

输出结果为

0.0 0.9960785

数据的最小值是0,最大值接近1.这说明数据已经经过标准化处理了,如果没有标准化处理图片的像素值是在0~255之间的.

我们要打印出来一张图片看看是什么样子的,先将数据reshape成28*28的矩阵,然后打印图片,注意:照片是黑白的,所以打印图片的时候要加cmap='Greys'参数

im = im.reshape(28, 28)
print(im.shape)
plt.imshow(im, cmap='Greys')
plt.show()


这里有一个问题,图片中大多数的点都是白色的,为什么大多数的数据值都是0呢?0不是应该为黑色么,没错这张图片确实应该是黑色的.我们可以将cmap='Greys'改为cmap='gray',会得到这样的结果

这应该是MNIST数据集做的一种数据预处理,毕竟计算0比计算255更容易不是么.

打印对应的标记

print(mnist.train.labels[0])

得到了一个one-hot encoding的编码

[0. 0. 0. 0. 0. 0. 0. 1. 0. 0.]

打印前几张7

indexs = np.where(train_labels == 7)

fig, ax = plt.subplots(
    nrows=5,
    ncols=5,
    sharex='all',
    sharey='all', )

ax = ax.flatten()
for i in range(25):
    img = train_image[indexs[0][i]].reshape(28, 28)
    ax[i].imshow(img, cmap='Greys', interpolation='nearest')

ax[0].set_xticks([])
ax[0].set_yticks([])
plt.tight_layout()
plt.show()


多打印几张图片看看

fig, ax = plt.subplots(
    nrows=5,
    ncols=5,
    sharex='all',
    sharey='all', )

ax = ax.flatten()
for i in range(25):
    img = mnist.train.images[i].reshape(28, 28)
    ax[i].imshow(img, cmap='Greys', interpolation='nearest')

ax[0].set_xticks([])
ax[0].set_yticks([])
plt.tight_layout()
plt.show()


画一个直方图,看看数据集中各个数字的数据量

X = []
Y = []

for i in range(10):
    x = i
    y = np.sum(train_labels == i)
    X.append(x)
    Y.append(y)
    plt.text(x, y, '%s' % y, ha='center', va= 'bottom')

plt.bar(X, Y, facecolor='#9999ff', edgecolor='white')
plt.xticks(X)
plt.show()


参考:

  • https://en.wikipedia.org/wiki/MNIST_database
  • http://yann.lecun.com/exdb/mnist/
  • https://docs.python.org/3.6/library/collections.html
  • http://colah.github.io/posts/2014-10-Visualizing-MNIST/
  • https://matplotlib.org/users/colormaps.html
posted @ 2018-03-15 20:02:09
评论加载中...

发表评论