ARCore之路-计算机视觉之实例(一)

版权声明:David Wang原创ARCore文章,仅供学习研究之用,不得用于任何商业目的,未经授权不得转载! https://blog.csdn.net/yolon3000/article/details/84887067

  有了前面的基础,我们接下来将亲自编写一个神经网络,我们力求代码简洁并易于理解,性能则不是考虑的重点。当然,完完全全讲清神经网络还是比较困难的,这需要一些前置知识,也不是我们关注的重点,我们的重点是利用神经网络来服务AR应用。在开始之前,我们再来看一下人类大脑的神经。

DavidWang原创
  这个神经结构我们可以简化成如下图:
DavidWang原创

  这个图我们可以这样看,一个简单的神经可以由一个个的神经元细胞(Neuron )和连接神经细胞的突触(Synapse)相互连接而成。因此,我们可以把神经元细胞抽象为下图所示结构:

DavidWang原创

  一个神经元细胞可以有若干个输入,也可以有若干个输出,每个输入都通过一个突触与外界相连,同理,每一个输出也通过一个突触与外界相连。一个突触实际就是一个通道,连接不同的神经元。所示我们把突触抽象成如下图:

DavidWang原创

  突触的两端是两个神经元细胞,它把两个神经元细胞连接起来,在神经细胞间传递信息。基于上面的分析,我们可以分别编写突触与神经元细胞的代码如下:

public class Neuron 
{
    private static readonly System.Random Random = new System.Random();
    public List<Synapse> InputSynapses;
    public List<Synapse> OutputSynapses;
    public double Bias;
    public double BiasDelta;
    public double Gradient;
    public double Value;

    public Neuron()
    {
        InputSynapses = new List<Synapse>();
        OutputSynapses = new List<Synapse>();
        Bias = GetRandom();
    }

    public Neuron(IEnumerable<Neuron> inputNeurons) : this()
    {
        foreach (var inputNeuron in inputNeurons)
        {
            var synapse = new Synapse(inputNeuron, this);
            inputNeuron.OutputSynapses.Add(synapse);
            InputSynapses.Add(synapse);
        }
    }
}

public class Synapse
{
    public Neuron InputNeuron;
    public Neuron OutputNeuron;
    public double Weight;
    public double WeightDelta;
    public Synapse(Neuron inputNeuron, Neuron outputNeuron)
    {
        InputNeuron = inputNeuron;
        OutputNeuron = outputNeuron;
        Weight = Neuron.GetRandom();
    }
}

  在神经元类(Neuron )的定义中,我们定义了若干输入突触(Synapse),也定义了若干输出突触(Synapse),这就是public List InputSynapses 与 public List OutputSynapses;然后我们还定义了偏置值Bias。设置偏置值的目的是对结果做一个偏移(神经元的输出其实已经一种分类法,有输出为一类,没有输出是另一类)。理解偏置很简单,如假设我们有直线方程x1+x2-3=0,画出这个图像如下:

DavidWang原创
  在x1+x2-3=0方程里,-3就是偏置值,如果为0,则x1+x2=0的图像如下:
DavidWang原创
  直观的说,就是对输出做了一个偏移。在神经元类中,BiasDelta是偏置值增量,Gradient是梯度值,用于梯度下降算法,以收敛函数,Value为该神经元的计算值,这是一个综合了所有输入后神经元细胞本身的一个输出值。神经元类Neuron有两个构造函数,一个无参构造函数只是简单的初始化一个无输入无输出的神经元细胞;另一个有参构造函数构建一个带若干输入的神经元细胞。

  在突触类(Synapse)的定义很简单,连接一个输入神经元细胞,连接一个输出神经元细胞。还有一个权重值以及一个权重值增量。这个类定义很简单,不详述。
  因为神经元还应该具备逻辑思考(计算),训练更新突触(Synapse)权重的作用,所以我们还需要把神经元类Neuron扩展成如下:

public class Neuron
{
    private static readonly System.Random Random = new System.Random();
    public List<Synapse> InputSynapses;
    public List<Synapse> OutputSynapses;
    public double Bias;
    public double BiasDelta;
    public double Gradient;
    public double Value;

    public static double GetRandom()
    {
        return 2 * Random.NextDouble() - 1;
    }

    public Neuron()
    {
        InputSynapses = new List<Synapse>();
        OutputSynapses = new List<Synapse>();
        Bias = GetRandom();
    }

    public Neuron(IEnumerable<Neuron> inputNeurons) : this()
    {
        foreach (var inputNeuron in inputNeurons)
        {
            var synapse = new Synapse(inputNeuron, this);
            inputNeuron.OutputSynapses.Add(synapse);
            InputSynapses.Add(synapse);
        }
    }

    public virtual double CalculateValue()
    {
        return Value = Sigmoid.Output(InputSynapses.Sum(a => a.Weight * a.InputNeuron.Value) + Bias);
    }

    public double CalculateError(double target)
    {
        return target - Value;
    }

    public double CalculateGradient(double? target = null)
    {
        if (target == null)
            return Gradient = OutputSynapses.Sum(a => a.OutputNeuron.Gradient * a.Weight) * Sigmoid.Derivative(Value);

        return Gradient = CalculateError(target.Value) * Sigmoid.Derivative(Value);
    }

    public void UpdateWeights(double learnRate, double momentum)
    {
        var prevDelta = BiasDelta;
        BiasDelta = learnRate * Gradient;
        Bias += BiasDelta + momentum * prevDelta;

        foreach (var synapse in InputSynapses)
        {
            prevDelta = synapse.WeightDelta;
            synapse.WeightDelta = learnRate * Gradient * synapse.InputNeuron.Value;
            synapse.Weight += synapse.WeightDelta + momentum * prevDelta;
        }
    }

}

  GetRandom()方法获取一个[-1,1]之间的随机值。CalculateValue()方法使用Sigmoid激活函数获取当前神经元细胞的输出值。CalculateGradient()方法计算一个梯度值以便训练调整突触的权重值。UpdateWeights()方法用于训练更新突触的权重值。这里只着重解释一下Sigmoid激活函数,Sigmoid激活函数定义如下:

DavidWang原创
  Sigmoid激活函数曲线如下:
DavidWang原创
  Sigmoid激活函数性质:
  • 定义域:(−∞,+∞)
  • 值域:(−1,1)
  • 函数在定义域内为连续和光滑函数
  • 处处可导,导数为:f′(x)=f(x)(1−f(x))

  Sigmoid激活函数的这些性质非常类似神经元细胞的工作方式,一个神经元细胞在获取多个输入后,经过其本身的逻辑思考(计算),这个神经元细胞可以有电平输出以便传递信息,也可以不输出。事实上,通过训练后的神经网络,对于一个未知输入,往往不能得到100%的预测,但却可以得到一个发生的可能性,通常我们认为超过50%则认为事件发生(即有输出),低于50%则认为事件不发生(即无输出)。Sigmoid类的定义如下:

public static class Sigmoid
{
    public static double Output(double x)
    {
        return x < -45.0 ? 0.0 : x > 45.0 ? 1.0 : 1.0 / (1.0 + Mathf.Exp((float)-x));
    }

    public static double Derivative(double x)
    {
        return x * (1 - x);
    }
}

  为了减轻计算压力,输入小于-45我们直接返回0,输入大于45我们直接返回1,处于这两者之间的值我们才进行计算。至此,我们已完成了神经网络最基本的神经元及突触的模拟代码编写。有了这两个最基本的要素,下面我们将构建一个神经网络。

猜你喜欢

转载自blog.csdn.net/yolon3000/article/details/84887067