技术图文:如何利用 C# 实现 误差反向传播 学习规则?

背景

我们在 如何利用 C# 对神经网络模型进行抽象? 中完成了神经网络的抽象结构:

  • 三个接口:激活函数、有监督学习、无监督学习

接口

  • 三个抽象类:神经元、网络层、网络拓扑

抽象类

我们在 如何利用 C# 实现神经网络的感知器模型? 中对神经网络的结构进行了扩展,完成了感知器神经网络:

  • 实现了三个接口:利用符号函数和阈值函数实现激活函数接口;利用感知器学习规则实现有监督学习接口。

接口实现

  • 覆写了三个抽象类中的抽象方法:用实体类 ActivationLayer 继承 Layer;用实体类 ActivationNeuron 继承 Neuron;用实体类 ActivationNetwork 继承 Network

实体类继承

实体类继承

我们在 如何利用 C# 实现 Delta 学习规则? 中对神经网络进行进一步的扩展。

  • 实现了两个接口:利用Sigmoid函数实现激活函数接口;利用 Delta 学习规则实现有监督学习接口。

接口实现


技术分析

今天,我们进一步扩展神经网络的学习规则,利用误差反向传播学习规则来实现有监督学习接口;增加BP神经网络另外一个常用的Sigmoid函数。

接口实现

有关BP神经网络的原理,参见图文 基于Matlab的BP神经网络在语音特征信号识别中的应用,这篇图文中有详细的权值、阈值推导过程。


代码实现

双极性Sigmoid函数

public class BipolarSigmoidFunction : IActivationFunction
{
    public double Alpha { get; set; }
    public BipolarSigmoidFunction(double alpha=2)
    {
        Alpha = alpha;
    }

    public double Function(double x)
    {
        return 2 / (1 + Math.Exp(-Alpha * x)) - 1;
    }

    public double Derivative(double x)
    {
        double y = Function(x);

        return Alpha * (1 - y * y) / 2;
    }

    public double Derivative2(double y)
    {
        return Alpha * (1 - y * y) / 2;
    }
}

误差反传算法的实现

public class BackPropagationLearning : ISupervisedLearning
{
    private readonly ActivationNetwork _network;
    private double _learningRate = 0.1;
    private readonly double[][] _neuronErrors;
    private readonly double[][][] _weightsUpdates;
    private readonly double[][] _thresholdsUpdates;

    public double LearningRate
    {
        get { return _learningRate; }
        set { _learningRate = Math.Max(0.0, Math.Min(1.0, value)); }
    }

    public BackPropagationLearning(ActivationNetwork network)
    {
        _network = network;
        _neuronErrors = new double[network.Layers.Length][];
        _weightsUpdates = new double[network.Layers.Length][][];
        _thresholdsUpdates = new double[network.Layers.Length][];

        for (int i = 0; i < network.Layers.Length; i++)
        {
            Layer layer = network.Layers[i];

            _neuronErrors[i] = new double[layer.Neurons.Length];
            _weightsUpdates[i] = new double[layer.Neurons.Length][];
            _thresholdsUpdates[i] = new double[layer.Neurons.Length];

            for (int j = 0; j < _weightsUpdates[i].Length; j++)
            {
                _weightsUpdates[i][j] = new double[layer.InputsCount];
            }
        }
    }

    public double Run(double[] input, double[] output)
    {
        // 计算网络的输出
        _network.Compute(input);
        // 计算各层的误差
        double error = CalculateError(output);
        // 计算权值和阈值的改变量
        CalculateUpdates(input);
        // 更新整个网络
        UpdateNetwork();
        return error;
    }

    public double RunEpoch(double[][] input, double[][] output)
    {
        double error = 0.0;

        for (int i = 0; i < input.Length; i++)
        {
            error += Run(input[i], output[i]);
        }

        return error;
    }
}    

计算各层的误差与网络的总体误差

private double CalculateError(double[] desiredOutput)
{
    double error = 0;
    int layersCount = _network.Layers.Length;
    ActivationNeuron activationNeuron = _network.Layers[0].Neurons[0] as ActivationNeuron;
    
    if (activationNeuron != null)
    {
        IActivationFunction function = 
            activationNeuron.ActivationFunction;
            
        Layer layer = _network.Layers[layersCount - 1];
        double[] errors = _neuronErrors[layersCount - 1];

        for (int i = 0; i < layer.Neurons.Length; i++)
        {
            double output = layer.Neurons[i].Output;
            double e = desiredOutput[i] - output;
            errors[i] = e * function.Derivative2(output);
            error += e * e;
        }
        for (int j = layersCount - 2; j >= 0; j--)
        {
            layer = _network.Layers[j];
            Layer layerNext = _network.Layers[j + 1];
            errors = _neuronErrors[j];
            double[] errorsNext = _neuronErrors[j + 1];

            for (int i = 0; i < layer.Neurons.Length; i++)
            {
                double sum = 0.0;
                for (int k = 0; k < layerNext.Neurons.Length; k++)
                {
                    sum += errorsNext[k] * layerNext.Neurons[k].Weights[i];
                }
                errors[i] = sum * function.Derivative2(layer.Neurons[i].Output);
            }
        }
    }
    return error / 2.0;
}

计算网络权值和阈值的调整值

private void CalculateUpdates(double[] input)
{
    Layer layer = _network.Layers[0];
    double[] errors = _neuronErrors[0];
    double[][] layerWeightsUpdates = _weightsUpdates[0];
    double[] layerThresholdUpdates = _thresholdsUpdates[0];

    for (int i = 0; i < layer.Neurons.Length; i++)
    {
        double error = errors[i];
        double[] neuronWeightUpdates = layerWeightsUpdates[i];

        for (int j = 0; j < neuronWeightUpdates.Length; j++)
        {
            neuronWeightUpdates[j] = _learningRate * error * input[j];
        }
        layerThresholdUpdates[i] = _learningRate * error;
    }

    for (int k = 1; k < _network.Layers.Length; k++)
    {
        Layer layerPrev = _network.Layers[k - 1];
        layer = _network.Layers[k];
        errors = _neuronErrors[k];
        layerWeightsUpdates = _weightsUpdates[k];
        layerThresholdUpdates = _thresholdsUpdates[k];

        for (int i = 0; i < layer.Neurons.Length; i++)
        {
            double error = errors[i];
            double[] neuronWeightUpdates = layerWeightsUpdates[i];

            for (int j = 0; j < neuronWeightUpdates.Length; j++)
            {
                neuronWeightUpdates[j] = _learningRate * error * layerPrev.Neurons[j].Output;
            }
            layerThresholdUpdates[i] = _learningRate * error;
        }
    }
}

调整整个网络的权值和阈值

private void UpdateNetwork()
{
    for (int i = 0; i < _network.Layers.Length; i++)
    {
        Layer layer = _network.Layers[i];
        double[][] layerWeightsUpdates = _weightsUpdates[i];
        double[]  layerThresholdUpdates = _thresholdsUpdates[i];

        for (int j = 0; j < layer.Neurons.Length; j++)
        {
            double[] neuronWeightUpdates = layerWeightsUpdates[j];
            ActivationNeuron neuron = layer.Neurons[j] as ActivationNeuron;
            for (int k = 0; k < neuron.Weights.Length; k++)
            {
                neuron.Weights[k] += neuronWeightUpdates[k];
            }
            neuron.Threshold += layerThresholdUpdates[j];
        }
    }
}

总结

我们仍然应用 基于Matlab的BP神经网络在语音特征信号识别中的应用 中的语音信号分类的例子,来说明 BP 神经网络的应用。

获取数据集的方法

static List<double[]> GetData()
{
    List<double[]> result = new List<double[]>();
    string[] files = Directory.GetFiles(@".\data");

    foreach (string file in files)
    {
        string[] dataset = File.ReadAllLines(file);
        foreach (string d in dataset)
        {
            if (string.IsNullOrEmpty(d))
                continue;
            string[] data = d.Split(',');
            double[] item = new double[data.Length];
            for (int i = 0; i < data.Length; i++)
            {
                item[i] = Convert.ToDouble(data[i]);
            }
            result.Add(item);
        }
    }
    result = result.OrderBy(c => Guid.NewGuid()).ToList();
    return result;
}

获取输入、输出样本的方法

private static void GetDataset(List<double[]> lst, out double[][] inputs, out double[][] outputs)
{
    int count = lst.Count;
    inputs = new double[count][];
    outputs = new double[count][];
    for (int i = 0; i < count; i++)
    {
        double[] data = lst[i];
        int k = (int) data[0];
        switch (k)
        {
            case 1:
                outputs[i] = new double[] {1, 0, 0, 0};
                break;
            case 2:
                outputs[i] = new double[] {0, 1, 0, 0};
                break;
            case 3:
                outputs[i] = new double[] { 0, 0, 1, 0 };
                break;
            case 4:
                outputs[i] = new double[] { 0, 0, 0, 1 };
                break;
        }

        inputs[i] = new double[data.Length - 1];
        for (int j = 0; j < inputs[i].Length; j++)
        {
            inputs[i][j] = data[j + 1];
        }
    }
}

主应用程序

  • 输入层 24 个神经元
  • 中间层 25 个神经元
  • 输出层 4 个神经元
  • 训练样本 1500 个
  • 测试样本 500 个
  • 最大迭代次数 1000 次
static void Main(string[] args)
{
    List<double[]> lst = GetData();
    double[][] input;
    double[][] output;

    int trainNum = 1500;
    List<double[]> trainData = lst.GetRange(0, trainNum);
    GetDataset(trainData, out input, out output);

    ActivationNetwork network = new ActivationNetwork(new SigmoidFunction(),
        24, new int[] {25, 4});
         
    ISupervisedLearning bp = new BackPropagationLearning(network);
            
    for (int i = 0; i < 1000; i++)
    {
        double error = bp.RunEpoch(input, output);
        Console.WriteLine("{0}:{1}",i,error);
        if (error <= 0.01)
            break;
    }

    List<double[]> testData = lst.GetRange(trainNum, lst.Count - trainNum);
    GetDataset(testData, out input, out output);
    int num = 0;
    for (int i = 0; i < testData.Count; i++)
    {
        double[] result = network.Compute(input[i]);
        int index = MaxIndex(result);
        if (output[i][index] == 1)
            num++;
    }
    Console.WriteLine("准确率为:{0}", 1.0*num/testData.Count);
}

最后得到的准确率为:91.2%,与用 Matlab 计算的近似。好了,今天就到这里吧!See You!


对了,到目前为止已经有 10 名同学 通过解码 Huffman Code 得到团队的报名方式。

团队的招新仍在进行中,对我们感兴趣的同学欢迎与我联系,我会亲自带着大家学习,一起成长!


相关图文

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

青少年编程备考

感谢您的支持!

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值