非工作径向基函数神经网络的建议

时间:2015-04-02 10:31:59

标签: c# winforms neural-network

我正在创建一个小型C#应用程序,以帮助研究多层感知器和径向基函数神经网络的不同设计。 MLP工作得很充分,但我无法让RBF网络工作。 我已经检查并仔细检查并对算法进行三重检查,以确定它们是否与在线以及论文和书籍中可用的算法相匹配,看起来就像是这样。我还检查了一些同事(工作)代码并将其与我的相比较,看看是否有任何我做错了或遗漏了什么都没找到。 所以我希望一些额外的眼睛和意见可以帮助我解决这个问题,因为我已经没有想到要尝试什么或在哪里看的想法。如果您想查看或下载整个项目,可以在https://bitbucket.org/floofykh/gameai-coursework

找到它

我还会在这里发布RBF特定代码。

这是Windows窗体,允许用户输入RBF网络的设计并对其进行训练。

public partial class RBFForm : Form
{
    private const double X_MAX = Math.PI * 2;
    private const double X_MIN = 0;
    private const double INTERVAL = Math.PI / 90d;
    private double m_numPlotPoints;
    private double m_noiseValue = 0;
    public double StopThreshold { get { return m_stopThreshold; } }
    private double m_stopThreshold;
    private string m_function = "";
    private List<double> m_inputs, m_targets;
    private List<RadialBasisFunctionData> m_rbfsData;
    private RadialBasisFunctionNetwork m_rbf;
    private int m_numRBFs = 1;
    private double m_rbfWidth = 1d, m_rbfOffset = 0d, m_rbfSeperation = 0d;
    private bool m_changed = true;
    private const int testCases = 180;

    public RBFForm()
    {
        InitializeComponent();
        ChartArea functionArea = m_functionGraph.ChartAreas.Add("function");
        functionArea.AxisX.Maximum = X_MAX;
        functionArea.AxisX.Minimum = X_MIN;
        functionArea.AxisY.Maximum = 1.5;
        functionArea.AxisY.Minimum = -1.5;
        ChartArea rbfArea = m_rbfGraph.ChartAreas.Add("RBFs");
        rbfArea.AxisX.Maximum = X_MAX;
        rbfArea.AxisX.Minimum = X_MIN;
        rbfArea.AxisY.Maximum = 1;
        rbfArea.AxisY.Minimum = 0;

        m_functionGraph.Series.Add("Neural Network");
        m_functionGraph.Series.Add("Function");
        m_functionGraph.Series.Add("Points");
        m_rbfGraph.Series.Add("RBFs");

        Neuron.LearningRate = ((double)(m_learningRateSelector).Value);
        m_numRBFs = ((int)(m_numRBFsInput).Value);
        m_rbfOffset = ((double)(m_rbfOffsetController).Value);
        m_rbfSeperation = ((double)(m_rbfOffsetController).Value);
        m_rbfWidth = ((double)(m_rbfWidthController).Value);

        m_rbf = new RadialBasisFunctionNetwork(this);
    }
    private void InitialiseFunctionGraph()
    {
        Series func = m_functionGraph.Series.FindByName("Function");
        func.Points.Clear();
        func.ChartType = SeriesChartType.Line;
        func.Color = Color.Green;
        func.BorderWidth = 1;

        for (double x = X_MIN; x < X_MAX; x += INTERVAL)
        {
            double y = 0;
            switch (m_function)
            {
                case "Sin":
                    y = Math.Sin(x);
                    break;
                case "Cos":
                    y = Math.Cos(x);
                    break;
            };
            func.Points.AddXY(x, y);
        }
    }

    private void InitialiseRBFs()
    {
        m_rbfsData = new List<RadialBasisFunctionData>();
        Series rbfs = m_rbfGraph.Series.FindByName("RBFs");
        rbfs.Points.Clear();
        rbfs.ChartType = SeriesChartType.Line;
        rbfs.Color = Color.IndianRed;
        rbfs.BorderWidth = 1;

        for(int i=0; i<m_numRBFs; i++)
        {
            double centre = X_MIN + m_rbfOffset + m_rbfSeperation * i;
            RadialBasisFunctionData data = new RadialBasisFunctionData();
            data.Centre = centre;
            data.Width = m_rbfWidth;
            m_rbfsData.Add(data);
            DrawRBF(centre, m_rbfWidth, rbfs.Points);
        }
    }

    private void DrawRBF(double centre, double width, DataPointCollection points)
    {
        if(width > 0)
        {
            IActivationFunction function = new RadialBasisFunction(centre, width);
            for (double x = X_MIN; x < X_MAX; x += INTERVAL)
            {
                double y = function.Function(x);
                points.AddXY(x, y);
            }
        }
    }

    private void InitialiseInputPoints()
    {
        m_inputs = new List<double>();
        m_targets = new List<double>();

        Series points = m_functionGraph.Series.FindByName("Points");
        points.Points.Clear();
        points.ChartType = SeriesChartType.Point;
        points.Color = Color.Blue;
        points.BorderWidth = 1;

        double interval = 0d;
        if (m_numPlotPoints > 1)
            interval = (X_MAX - X_MIN) / (m_numPlotPoints - 1);

        for (int point = 0; point < m_numPlotPoints; point++)
        {
            double x = X_MIN + point * interval;
            double y = 0;
            switch (m_function)
            {
                case "Sin":
                    y = Math.Sin(x);
                    break;
                case "Cos":
                    y = Math.Cos(x);
                    break;
            };

            y += (Program.rand.NextDouble() - 0.5d) * 2d * m_noiseValue;
            m_targets.Add(y);
            m_inputs.Add(x);
            points.Points.AddXY(x, y);
        }
    }
    public void SetNumEpochs(int num)
    {
        m_numEpochLabel.Text = num.ToString();
    }

    public void SetNumEpochsAsync(int num)
    {
        try
        {
            if (m_numEpochLabel.InvokeRequired)
            {
                m_numEpochLabel.Invoke((MethodInvoker)delegate
                {
                    m_numEpochLabel.Text = num.ToString();
                });
            }
        }
        catch (Exception) { }
    }

    private void m_rbfSeperationController_ValueChanged(object sender, EventArgs e)
    {
        double value = ((double)((NumericUpDown)sender).Value);
        m_rbfSeperation = value;
        InitialiseRBFs();
        m_changed = true;
    }

    private void m_numRBFsInput_ValueChanged(object sender, EventArgs e)
    {
        int value = ((int)((NumericUpDown)sender).Value);
        m_numRBFs = value;
        InitialiseRBFs();
        m_changed = true;
    }

    private void m_rbfWidthController_ValueChanged(object sender, EventArgs e)
    {
        double value = ((double)((NumericUpDown)sender).Value);
        m_rbfWidth = value;
        InitialiseRBFs();
        m_changed = true;
    }

    private void m_rbfOffsetController_ValueChanged(object sender, EventArgs e)
    {
        double value = ((double)((NumericUpDown)sender).Value);
        m_rbfOffset = value;
        InitialiseRBFs();
        m_changed = true;
    }

    private void m_learningRateSelector_ValueChanged(object sender, EventArgs e)
    {
        double value = ((double)((NumericUpDown)sender).Value);
        Neuron.LearningRate = value;
        m_changed = true;
    }

    private void m_momentumController_ValueChanged(object sender, EventArgs e)
    {
        double value = ((double)((NumericUpDown)sender).Value);
        Neuron.MomentumAlpha = value;
        m_changed = true;
    }

    private void m_thresholdController_ValueChanged(object sender, EventArgs e)
    {
        double value = ((double)((NumericUpDown)sender).Value);
        m_stopThreshold = value;
        m_changed = true;
    }

    private void m_functionSelector_SelectedIndexChanged(object sender, EventArgs e)
    {
        m_function = ((ComboBox)sender).SelectedItem.ToString();
        InitialiseFunctionGraph();
        m_changed = true;
    }

    private void m_plotPointsController_ValueChanged(object sender, EventArgs e)
    {
        double value = ((double)((NumericUpDown)sender).Value);
        m_numPlotPoints = value;
        InitialiseInputPoints();
        m_changed = true;
    }

    private void m_noiseController_ValueChanged(object sender, EventArgs e)
    {
        double value = ((double)((NumericUpDown)sender).Value);
        m_noiseValue = value;
        InitialiseInputPoints();
        m_changed = true;
    }

    private void m_trainButton_Click(object sender, EventArgs e)
    {
        if (m_rbf != null)
        {
            if (RadialBasisFunctionNetwork.Running)
            {
                RadialBasisFunctionNetwork.Running = false;
            }
            else
            {
                if (m_changed)
                {
                    m_rbf.Initialise(1, m_rbfsData, 1);
                    m_changed = false;
                }

                RadialBasisFunctionNetwork.ErrorStopThreshold = m_stopThreshold;
                List<List<double>> inputPatterns = new List<List<double>>();
                List<List<double>> targetPatterns = new List<List<double>>();

                for (int i = 0; i < m_inputs.Count; i++)
                {
                    List<double> newInputPattern = new List<double>();
                    newInputPattern.Add(m_inputs[i]);
                    List<double> newTargetPattern = new List<double>();
                    newTargetPattern.Add(m_targets[i]);

                    inputPatterns.Add(newInputPattern);
                    targetPatterns.Add(newTargetPattern);

                }

                m_rbf.Train(inputPatterns, targetPatterns);
            }
        }
    }

    public void TestAndPresent()
    {
        List<double> finalData = new List<double>();

        for (double x = X_MIN; x < X_MAX; x += INTERVAL)
        {
            List<double> input = new List<double>();
            input.Add(x);

            finalData.AddRange(m_rbf.Test(input));
        }
        PlotNeuralOutput(finalData);
    }
    public void TestAndPresentAsync()
    {
        List<double> finalData = new List<double>();

        for (double x = X_MIN; x < X_MAX; x += INTERVAL)
        {
            List<double> input = new List<double>();
            input.Add(x);

            finalData.AddRange(m_rbf.Test(input));
        }
        PlotNeuralOutputAsync(finalData);
    }
    public void PlotNeuralOutput(List<double> output)
    {
        Series network = m_functionGraph.Series["Neural Network"];

        network.Points.Clear();
        network.ChartType = SeriesChartType.Line;
        network.Color = Color.Red;
        network.BorderWidth = 3;

        double x = 0;
        for (int i = 0; i < output.Count; i++)
        {
            network.Points.AddXY(x, output[i]);
            x += INTERVAL;
        }
    }
    public void PlotNeuralOutputAsync(List<double> output)
    {
        try
        {
            if (m_functionGraph.InvokeRequired)
            {
                m_functionGraph.Invoke((MethodInvoker)delegate
                {
                    Series network = m_functionGraph.Series["Neural Network"];

                    network.Points.Clear();
                    network.ChartType = SeriesChartType.Line;
                    network.Color = Color.Red;
                    network.BorderWidth = 3;

                    double x = 0;
                    for (int i = 0; i < output.Count; i++)
                    {
                        network.Points.AddXY(x, output[i]);
                        x += INTERVAL;
                    }
                });
            }
        }
        catch (Exception) { }
    }
}

这是RadialBasisFunction类,其中大部分RBF算法发生,具体在FeedForward()中。

class RadialBasisFunctionNetwork
{

    private NeuronLayer m_inputLayer;
    private NeuronLayer m_radialFunctions;
    private NeuronLayer m_outputLayer;

    private int m_numRadialFunctions = 0;
    public static bool Running = false;

    public static double ErrorStopThreshold {get; set;}
    private static int m_epoch = 0;
    public static int Epoch { get { return m_epoch; } }

    private RBFForm m_RBFForm = null;

    public RadialBasisFunctionNetwork(RBFForm RBFForm)
    {
        m_RBFForm = RBFForm;
        m_inputLayer = new NeuronLayer();
        m_radialFunctions = new NeuronLayer();
        m_outputLayer = new NeuronLayer();
    }

    public void Initialise(int numInputs, List<RadialBasisFunctionData> radialFunctions, int numOutputs)
    {
        ErrorStopThreshold = 0d;
        m_epoch = 0;
        m_numRadialFunctions = radialFunctions.Count;

        m_inputLayer.Neurons.Clear();
        //Add bias neuron
        /*Neuron inputBiasNeuron = new Neuron(1d);
        inputBiasNeuron.Initialise(m_numRadialFunctions);
        m_inputLayer.Neurons.Add(inputBiasNeuron);*/
        for(int i=0; i<numInputs; i++)
        {
            Neuron newNeuron = new Neuron();
            newNeuron.Initialise(m_numRadialFunctions);
            m_inputLayer.Neurons.Add(newNeuron);
        }

        m_outputLayer.Neurons.Clear();
        for (int i = 0; i < numOutputs; i++)
        {
            Neuron newNeuron = new Neuron();
            m_outputLayer.Neurons.Add(newNeuron);
        }

        m_radialFunctions.Neurons.Clear();
        //Add bias neuron
       /* Neuron outputBiasNeuron = new Neuron(1d);
        outputBiasNeuron.Initialise(numOutputs);
        outputBiasNeuron.ActivationFunction = new ConstantActivationFunction();
        m_radialFunctions.Neurons.Add(outputBiasNeuron);*/
        for (int i = 0; i < m_numRadialFunctions; i++)
        {
            Neuron newNeuron = new Neuron();
            newNeuron.Initialise(numOutputs);
            newNeuron.ActivationFunction = new RadialBasisFunction(radialFunctions[i].Centre, radialFunctions[i].Width);
            m_radialFunctions.Neurons.Add(newNeuron);
        }
    }

    public void Train(List<List<double>> inputs, List<List<double>> targets)
    {
        Running = true;
        BackgroundWorker bw = new BackgroundWorker();

        bw.DoWork += new DoWorkEventHandler(
            delegate(object o, DoWorkEventArgs args)
            {
                while (Running)
                {
                    TrainPatterns(inputs, targets);

                    m_RBFForm.SetNumEpochsAsync(m_epoch);
                    m_RBFForm.TestAndPresentAsync();
                }
            });

        bw.RunWorkerAsync();
    }

    private void TrainPatterns(List<List<double>> inputs, List<List<double>> targets)
    {
        Queue<int> randomIndices = GenRandomNonRepNumbers(inputs.Count, 0, inputs.Count, Program.rand);

        bool trained = true;
        while(randomIndices.Count > 0)
        {
            int index = randomIndices.Dequeue();
            TrainPattern(inputs[index], targets[index]);
            foreach(Neuron neuron in m_outputLayer.Neurons)
            {
                if (Math.Abs(neuron.Error) > ErrorStopThreshold)
                    trained = false;
            }
        }
        m_epoch++;

        if (trained)
            Running = false;
    }

    public void TrainPattern(List<double> inputs, List<double> targets)
    {
        InitialisePatternSet(inputs, targets);
        FeedForward();
    }

    public List<double> Test(List<double> inputs)
    {
        InitialisePatternSet(inputs, null);
        FeedForward();
        return GetOutput();
    }

    public void FeedForward()
    {
        //Feed from input
        for(int i=0; i<m_radialFunctions.NumNeurons; i++)
        {
            Neuron radialFunctionNeuron = m_radialFunctions.Neurons[i];
            radialFunctionNeuron.Output = 0d;
            for(int j=0; j<m_inputLayer.NumNeurons; j++)
            {
                Neuron inputNeuron = m_inputLayer.Neurons[j];
                radialFunctionNeuron.Output += radialFunctionNeuron.ActivationFunction.Function(inputNeuron.Output);
            }
        }

        //Feed to output
        for (int i = 0; i < m_outputLayer.NumNeurons; i++)
        {
            Neuron outputNeuron = m_outputLayer.Neurons[i];
            outputNeuron.Output = 0d;
            for (int j = 0; j < m_radialFunctions.NumNeurons; j++)
            {
                Neuron radialFunctionNeuron = m_radialFunctions.Neurons[j];
                outputNeuron.Output += radialFunctionNeuron.Weight(i) * radialFunctionNeuron.Output;
            }
            outputNeuron.Error = (outputNeuron.Target - outputNeuron.Output);
        }

        //Update weights
        for (int i = 0; i < m_radialFunctions.NumNeurons; i++)
        {
            Neuron radialFunctionNeuron = m_radialFunctions.Neurons[i];
            for (int j = 0; j < m_outputLayer.NumNeurons; j++)
            {
                Neuron outputNeuron = m_outputLayer.Neurons[j];
                if(Math.Abs(outputNeuron.Error) > m_RBFForm.StopThreshold)
                    radialFunctionNeuron.m_weights[j] += Neuron.LearningRate * outputNeuron.Error * radialFunctionNeuron.Output;
            }
        }
    }

    public List<double> GetOutput()
    {
        List<double> output = new List<double>();
        for (int i = 0; i < m_outputLayer.NumNeurons; i++)
        {
            output.Add(m_outputLayer.Neurons[i].Output);
        }
        return output;
    }

    private void InitialisePatternSet(List<double> inputs, List<double> targets)
    {
        m_inputLayer.SetInputs(inputs, false);

        if(targets != null)
        {
            m_outputLayer.SetTargets(targets);
        }
    }

    private Queue<int> GenRandomNonRepNumbers(int num, int min, int max, Random generator)
    {
        if (max - min < num)
            return null;

        Queue<int> numbers = new Queue<int>(num);

        for (int i = 0; i < num; i++)
        {
            int randNum = 0;
            do
            {
                randNum = generator.Next(min, max);
            } while (numbers.Contains(randNum));

            numbers.Enqueue(randNum);
        }

        return numbers;
    }
}

这是我用作激活功能的径向基函数

class RadialBasisFunction : IActivationFunction
{
    private double m_centre = 0d, m_width = 0d;

    public RadialBasisFunction(double centre, double width)
    {
        m_centre = centre;
        m_width = width;
    }

    double IActivationFunction.Function(double activation)
    {
        double dist = activation - m_centre;
        return Math.Exp(-(dist * dist) / (2 * m_width * m_width));
        //return Math.Exp(-Math.Pow(dist / (2 * m_width), 2d));
        //return Math.Exp(-Math.Pow(dist, 2d));
    }
}

NeuronLayer类实际上只是一个神经元列表的包装,并不再是完全必要的,但我一直专注于让一切工作,而不是保持我的代码干净和精心设计。

class NeuronLayer
{
    public int NumNeurons { get { return Neurons.Count; } }
    public List<Neuron> Neurons { get; set; }

    public NeuronLayer ()
    {
        Neurons = new List<Neuron>();
    }

    public void SetInputs(List<double> inputs, bool skipBias)
    {
        for (int i = 0; i < Neurons.Count; i++)
        {
            if(skipBias)
            {
                if (i != 0)
                    Neurons[i].Input = inputs[i-1];
            }
            else
            {
                Neurons[i].Input = inputs[i];
            }
        }
    }

    public void SetTargets(List<double> targets)
    {
        for (int i = 0; i < Neurons.Count; i++)
        {
            Neurons[i].Target = targets[i];
        }
    }
}

最后是神经元课。这个课程是在我编写MLP时编写的,而我仍在努力弄清楚神经网络是如何工作的。所以不幸的是,其中的很多代码都是针对MLP的。我希望一旦我完成所有工作就可以改变这一点,并且可以开始清理所有内容并使应用程序更加用户友好。我要添加所有功能以保证完整性,但我已经检查并仔细检查过,我不应该在RBF网络中的任何地方使用任何MLP特定的Neuron代码。 MLP特定的东西是WithinThreshold,以及Weight(int)之后的所有函数。

class Neuron
{
    private IActivationFunction m_activationFunction = null;
    public IActivationFunction ActivationFunction { get { return m_activationFunction; } set { m_activationFunction = value; } }

    public double Input { get { return Output; } set { Output = value; } }
    public double Output { get; set; }
    public double Error { get; set; }
    public double Target { get; set; }

    private double m_activation = 0d;

    public bool WithinThreshold { get { return Math.Abs(Error) < MultilayerPerceptron.ErrorStopThreshold; } }

    public static double LearningRate { get; set; }
    public static double MomentumAlpha { get; set; }

    public List<double> m_weights;
    private List<double> m_deltaWeights;

    public Neuron()
    {
        Output = 0d;
        m_weights = new List<double>();
        m_deltaWeights = new List<double>();
        m_activationFunction = new TanHActFunction();
    }

    public Neuron(double input)
    {
        Input = input;
        Output = input;
        m_weights = new List<double>();
        m_deltaWeights = new List<double>();
        m_activationFunction = new TanHActFunction();
    }

    public void Initialise(int numWeights)
    {
        for(int i=0; i<numWeights; i++)
        {
            m_weights.Add(Program.rand.NextDouble()*2d - 1d);
        }
    }

    public double Weight(int index)
    {
        if (m_weights != null && m_weights.Count > index)
            return m_weights[index];

        return 0d;
    }

    public void Feed(NeuronLayer layer, int neuronIndex)
    {
        List<Neuron> inputNeurons = layer.Neurons;
        m_activation = 0;

        for (int j = 0; j < layer.NumNeurons; j++)
        {
            m_activation += inputNeurons[j].Output * inputNeurons[j].Weight(neuronIndex);
        }

        Output = m_activationFunction.Function(m_activation);
    }

    public void CalculateError(NeuronLayer successor, bool outputLayer)
    {
        if(outputLayer)
        {
            Error = (Target - Output) * ActivationFunction.FunctionDeriv(Output);
        }
        else
        {
            Error = 0d;
            for(int i=0; i<successor.NumNeurons; i++)
            {
                Neuron neuron = successor.Neurons[i];
                Error += (neuron.Error * m_weights[i] * ActivationFunction.FunctionDeriv(Output));
            }
        }
    }

    public void UpdateWeights(NeuronLayer successor)
    {
        if (MomentumAlpha != 0)
        {
            for (int i = 0; i < successor.NumNeurons; i++)
            {
                var neuron = successor.Neurons[i];

                if (m_deltaWeights.Count <= i)
                {
                    double deltaWeight = LearningRate * neuron.Error * Output;

                    m_weights[i] += deltaWeight;
                    m_deltaWeights.Add(deltaWeight);
                }
                else
                {
                    double deltaWeight = /*(1 - MomentumAlpha)*/LearningRate * neuron.Error * Output + MomentumAlpha * m_deltaWeights[i];

                    m_weights[i] += deltaWeight;
                    m_deltaWeights[i] = deltaWeight;
                }
            }
        }
        else
        {
            for (int i = 0; i < successor.NumNeurons; i++)
            {
                var neuron = successor.Neurons[i];
                double deltaWeight = LearningRate * neuron.Error * Output;
                m_weights[i] += deltaWeight;
            }
        }
    }
}

我希望一些额外的眼睛和意见有助于找到我的问题。

PS如果您确实从存储库下载了源代码并尝试运行该应用程序,请注意,如果您未在训练之前设置所有必需值,或者按下重置按钮,它将会中断。我应该摆脱重置按钮,但我还没有。遗憾!

1 个答案:

答案 0 :(得分:0)

我发现了问题所在。 我正在更新FeedForward方法中的权重。但是,在训练和测试网络时都会调用该方法。所以我在测试网络时不应该更新权重。