# 深入了解神经网络

James McCaffrey

## 实现

``````using System;
namespace NeuralNetworks
{
class NeuralNetworksProgram
{
static void Main(string[] args)
{
try
{
Console.WriteLine("\nBegin Neural Network demo\n");
NeuralNetwork nn = new NeuralNetwork(3, 4, 2);
double[] weights = new double[] {
0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1.0, 1.1, 1.2,
-2.0, -6.0, -1.0, -7.0,
1.3, 1.4, 1.5, 1.6, 1.7, 1.8, 1.9, 2.0,
-2.5, -5.0 };
nn.SetWeights(weights);
double[] xValues = new double[] { 1.0, 2.0, 3.0 };
double[] yValues = nn.ComputeOutputs(xValues);
Helpers.ShowVector(yValues);
Console.WriteLine("End Neural Network demo\n");
}
catch (Exception ex)
{
Console.WriteLine("Fatal: " + ex.Message);
}
}
}
class NeuralNetwork
{
// Class members here
public NeuralNetwork(int numInput, int numHidden, int numOutput) { ... }
public void SetWeights(double[] weights) { ... }
public double[] ComputeOutputs(double[] xValues) { ... }
private static double SigmoidFunction(double x) { ... }
private static double HyperTanFunction(double x) { ... }
}
public class Helpers
{
public static double[][] MakeMatrix(int rows, int cols) { ... }
public static void ShowVector(double[] vector) { ... }
public static void ShowMatrix(double[][] matrix, int numRows) { ... }
}
} // ns
``````

## NeuralNetwork 类

NeuralNetwork 类定义开始为：

``````class NeuralNetwork
{
private int numInput;
private int numHidden;
private int numOutput;
...
``````

``````private double[] inputs;
private double[][] ihWeights; // input-to-hidden
private double[] ihSums;
private double[] ihBiases;
private double[] ihOutputs;
private double[][] hoWeights;  // hidden-to-output
private double[] hoSums;
private double[] hoBiases;
private double[] outputs;
...
``````

``````public NeuralNetwork(int numInput, int numHidden, int numOutput)
{
this.numInput = numInput;
this.numHidden = numHidden;
this.numOutput = numOutput;
inputs = new double[numInput];
ihWeights = Helpers.MakeMatrix(numInput, numHidden);
ihSums = new double[numHidden];
ihBiases = new double[numHidden];
ihOutputs = new double[numHidden];
hoWeights = Helpers.MakeMatrix(numHidden, numOutput);
hoSums = new double[numOutput];
hoBiases = new double[numOutput];
outputs = new double[numOutput];
}
``````

SetWeights 方法接受一组权重和偏移值，并且填充 ihWeights、ihBiases、hoWeights 和 hoBiases。 该方法的开头如下所示：

``````public void SetWeights(double[] weights)
{
int numWeights = (numInput * numHidden) +
(numHidden * numOutput) + numHidden + numOutput;
if (weights.Length != numWeights)
throw new Exception("xxxxxx");
int k = 0;
...
``````

``````for (int i = 0; i < numInput; ++i)
for (int j = 0; j < numHidden; ++j)
ihWeights[i][j] = weights[k++];
for (int i = 0; i < numHidden; ++i)
ihBiases[i] = weights[k++];
for (int i = 0; i < numHidden; ++i)
for (int j = 0; j < numOutput; ++j)
hoWeights[i][j] = weights[k++];
for (int i = 0; i < numOutput; ++i)
hoBiases[i] = weights[k++]
}
``````

weights 数组参数中的每个值将依次复制到 ihWeights、ihBiases、hoWeights 和 hoBiases 中。 请注意，不会有任何值复制到 ihSums 或 hoSums 中，因为这两个从头开始的数组将用于计算。

## 计算输出

NeuralNetwork 类的核心是 ComputeOutputs 方法。 此方法异常简短，开始为：

``````public double[] ComputeOutputs(double[] xValues)
{
if (xValues.Length != numInput)
throw new Exception("xxxxxx");
for (int i = 0; i < numHidden; ++i)
ihSums[i] = 0.0;
for (int i = 0; i < numOutput; ++i)
hoSums[i] = 0.0;
...
``````

``````for (int i = 0; i < xValues.Length; ++i)
this.inputs[i] = xValues[i];
for (int j = 0; j < numHidden; ++j)
for (int i = 0; i < numInput; ++i)
ihSums[j] += this.inputs[i] * ihWeights[i][j];
...
``````

xValues 数组参数中的值将复制到类输入数组成员中。 在某些神经网络方案中，输入参数值进行了标准化，例如，通过执行线性转换，使所有输入都在 -1.0 到 +1.0 之间，但是本例中没有进行标准化。 接着，嵌套的循环计算出加权和，如图 1图 3 所示。 请注意，为了以标准格式为 ihWeights 编制索引（其中 i 是行索引，j 是列索引），必须将 j 放在外层循环。 接着，ComputeOutputs 方法如下：

``````for (int i = 0; i < numHidden; ++i)
ihSums[i] += ihBiases[i];
for (int i = 0; i < numHidden; ++i)
ihOutputs[i] = SigmoidFunction(ihSums[i]);
...
``````

``````for (int j = 0; j < numOutput; ++j)
for (int i = 0; i < numHidden; ++i)
hoSums[j] += ihOutputs[i] * hoWeights[i][j];
for (int i = 0; i < numOutput; ++i)
hoSums[i] += hoBiases[i];
...
``````

``````for (int i = 0; i < numOutput; ++i)
this.outputs[i] = HyperTanFunction(hoSums[i]);
double[] result = new double[numOutput];
this.outputs.CopyTo(result, 0);
return result;
}
``````

## 激活函数和帮助器方法

``````private static double SigmoidFunction(double x)
{
if (x < -45.0) return 0.0;
else if (x > 45.0) return 1.0;
else return 1.0 / (1.0 + Math.Exp(-x));
}
``````

``````private static double HyperTanFunction(double x)
{
if (x < -10.0) return -1.0;
else if (x > 10.0) return 1.0;
else return Math.Tanh(x);
}
``````

Helpers 类中的静态实用工具方法只是为了编码方便。 NeuralNetwork 构造函数中用来分配矩阵的 MakeMatrix 方法将矩阵的每一行都实现为包含数组的数组：

``````public static double[][] MakeMatrix(int rows, int cols)
{
double[][] result = new double[rows][];
for (int i = 0; i < rows; ++i)
result[i] = new double[cols];
return result;
}
``````

ShowVector 和 ShowMatrix 方法将数组或矩阵中的值显示到控制台上。 您可以在随本文提供的代码下载中查看这两个方法的代码（请访问 archive.msdn.microsoft.com/mag201205TestRun）。

## 后续步骤

James McCaffrey 博士供职于 Volt Information Sciences, Inc.，负责管理对华盛顿州雷蒙德市 Microsoft 总部园区内的软件工程师进行的技术培训。 他参与过多项 Microsoft 产品的研发工作，其中包括 Internet Explorer 和 MSN Search。 他是《.NET Test Automation Recipes》(Apress, 2006) 的作者，您可以通过以下电子邮箱地址与他联系：jammc@microsoft.com