背景
我们在 如何利用 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 得到团队的报名方式。
团队的招新仍在进行中,对我们感兴趣的同学欢迎与我联系,我会亲自带着大家学习,一起成长!
相关图文:
- 如何利用 C# 实现 K 最邻近算法?
- 如何利用 C# 实现 K-D Tree 结构?
- 如何利用 C# + KDTree 实现 K 最邻近算法?
- 如何利用 C# 对神经网络模型进行抽象?
- 如何利用 C# 实现神经网络的感知器模型?
- 如何利用 C# 实现 Delta 学习规则?
- 如何利用 C# 爬取带 Token 验证的网站数据?
- 如何利用 C# 向 Access 数据库插入大量数据?
- 如何利用 C# 开发「桌面版百度翻译」软件!
- 如何利用 C# 开发「股票数据分析软件」(上)
- 如何利用 C# 开发「股票数据分析软件」(中)
- 如何利用 C# 开发「股票数据分析软件」(下)
- 如何利用 C# 爬取「财报说」中的股票数据?
- 如何利用 C# 爬取 One 持有者返利数据!
- 如何利用 C# 爬取Gate.io交易所的公告!
- 如何利用 C# 爬取BigOne交易所的公告!
- 如何利用 C# 爬取 ONE 的交易数据?
- 如何利用 C# 爬取「猫眼电影:热映口碑榜」及对应影片信息!
- 如何利用 C# 爬取「猫眼电影专业版:票房」数据!
- 如何利用 C# 爬取「猫眼电影:最受期待榜」及对应影片信息!
- 如何利用 C# 爬取「猫眼电影:国内票房榜」及对应影片信息!
- 如何利用 C# + Python 破解猫眼电影的反爬虫机制?
- 如何利用BigOne的API制作自动化交易系统 – 身份验证
- 如何利用BigOne的API制作自动化交易系统 – 获取账户资产
- 如何利用BigOne的API制作自动化交易系统 – 订单系统