这次详细第看了看WEKA里神经网络的实现,涉及的代码挺多的,我把跟图形化相关的删除了,另外有些地方我自己也不是很清楚,不过大体上还是很清楚的了,这是一项比较老的技术了,没什么好介绍的了,来这里看的各位估计都是懂的。直接贴代码了。
public void buildClassifier(Instances i) throws Exception { // 验证数据类型是否为算法所支持的类型 getCapabilities().testWithFail(i); // 读入数据集中的数据 i = new Instances(i); // 删除不完整的数据 i.deleteWithMissingClass(); // 如果只有类属性,转化为特殊处理 if (i.numAttributes() == 1) { System.err.println( "Cannot build model (only class attribute present in data!), " + "using ZeroR model instead!"); m_ZeroR = new weka.classifiers.rules.ZeroR(); m_ZeroR.buildClassifier(i); return; } else { m_ZeroR = null; } // 初始化遍历数据集的次数 m_epoch = 0; /** Shows the error of the epoch that the network just finished. */ m_error = 0; // 初始化训练集 m_instances = null; // 初始化正在经过神经网络的实例 m_currentInstance = null; // 初始化控制面板 m_controlPanel = null; // 初始化显示神经元的面板 m_nodePanel = null; // 初始化输出单元 m_outputs = new NeuralEnd[0]; // 初始化输入单元 m_inputs = new NeuralEnd[0]; // 初始化属性个数 m_numAttributes = 0; // 初始化类的分类数 m_numClasses = 0; // 初始化节点的连接逻辑 m_neuralNodes = new NeuralConnection[0]; // 是否需要停止神经网络 m_stopIt = true; // 神经网络是否已经停止 m_stopped = true; // 设置训练集 m_instances = new Instances(i); // 用一个随机化种子产生的随机数 m_random = new Random(m_randomSeed); // 重新排列数据集 m_instances.randomize(m_random); // 如果需要把数据集中的名词属性转化成二元属性 if (m_useNomToBin) { // 创建一个名词转二元属性的过滤器 m_nominalToBinaryFilter = new NominalToBinary(); // 使用过滤器过滤所有实例 m_nominalToBinaryFilter.setInputFormat(m_instances); m_instances = Filter.useFilter(m_instances,m_nominalToBinaryFilter); } // 属性的个数是数据集的属性个数减1,因为不包括类属性 m_numAttributes = m_instances.numAttributes() - 1; // 类的分类数是数据集类的分类数 m_numClasses = m_instances.numClasses(); // 归一化类属性到区间[-1,1] setClassType(m_instances); //初始化测试集 Instances valSet = null; //numinval is needed later int numInVal = (int)(m_valSize / 100.0 * m_instances.numInstances()); if (m_valSize > 0) { if (numInVal == 0) { numInVal = 1; } valSet = new Instances(m_instances, 0, numInVal); } /////////// // 创建输入单元 setupInputs(); // 创建输出单元 setupOutputs(); // 如果自动生成神经网络 if (m_autoBuild) { // 自动生成隐含层 setupHiddenLayer(); } // 如果类属性是数值属性 if (m_numeric) { setEndsToLinear(); } // 初始化向后传播误差 double right = 0; // 初始化停止继续重复训练的参数 double driftOff = 0; // 上次训练的向后传播误差 double lastRight = Double.POSITIVE_INFINITY; // 最小的向后传播误差 double bestError = Double.POSITIVE_INFINITY; // 加权学习率 double tempRate; // 训练集的总权重 double totalWeight = 0; // 测试集的总权重 double totalValWeight = 0; // 保存学习率的临时变量 double origRate = m_learningRate; //only used for when reset //确保至少有一个实例被训练 if (numInVal == m_instances.numInstances()) { numInVal--; } if (numInVal < 0) { numInVal = 0; } // 计算实例集的总权重 for (int noa = numInVal; noa < m_instances.numInstances(); noa++) { if (!m_instances.instance(noa).classIsMissing()) { totalWeight += m_instances.instance(noa).weight(); } } // 计算测试集的总权重 if (m_valSize != 0) { for (int noa = 0; noa < valSet.numInstances(); noa++) { if (!valSet.instance(noa).classIsMissing()) { totalValWeight += valSet.instance(noa).weight(); } } } m_stopped = false; // 遍历数据集m_numEpochs次 for (int noa = 1; noa < m_numEpochs + 1; noa++) { // 初始化向后传播误差 right = 0; // 遍历每个元组 for (int nob = numInVal; nob < m_instances.numInstances(); nob++){ // 取出当前的实例 m_currentInstance = m_instances.instance(nob); // 如果当前实例有类属性 if (!m_currentInstance.classIsMissing()) { // 重新初始化神经网络 resetNetwork(); // 计算加权和 calculateOutputs(); // 如果要进行权衰减,就随着遍历训练集的次数增多降低学习率 // 引入权值是因为公式要适应多个值的属性 tempRate = m_learningRate * m_currentInstance.weight(); if (m_decay) { tempRate /= noa; } // 累加向后传播的误差 right += (calculateErrors() / m_instances.numClasses()) * m_currentInstance.weight(); // 更新神经网络权值 updateNetworkWeights(tempRate, m_momentum); } } // 计算向后传播的平均误差 right /= totalWeight; // 如果向后传播的平均误差超过了一定的范围或者是一个非法值 if (Double.isInfinite(right) || Double.isNaN(right)) { // 如果没有设置自动重置神经网络 if (!m_reset) { // 清空数据集 m_instances = null; // 抛出异常:因为学习率过大造成神经网络无法被训练,请调低学习率 throw new Exception("Network cannot train. Try restarting with a" + " smaller learning rate."); } // 如果设置了自动重置神经网络 else { // 如果学习率太小抛出异常 if (m_learningRate <= Utils.SMALL) throw new IllegalStateException( "Learning rate got too small (" + m_learningRate + " <= " + Utils.SMALL + ")!"); // 将学习率调整为当前的一半 m_learningRate /= 2; // 重新构建神经网络 buildClassifier(i); m_learningRate = origRate; m_instances = new Instances(m_instances, 0); return; } } // 用独立的测试集测试 // 如果测试集不为空 if (m_valSize != 0) { // 如果向后传播误差为0 right = 0; // 遍历测试集的每个实例 for (int nob = 0; nob < valSet.numInstances(); nob++) { // 取出每个当前实例 m_currentInstance = valSet.instance(nob); // 如果确定了类值 if (!m_currentInstance.classIsMissing()) { // 重新初始化神经网络 resetNetwork(); // 计算加权和 calculateOutputs(); // 累加向后传播误差 right += (calculateErrors() / valSet.numClasses()) * m_currentInstance.weight(); } } // 如果当前的误差小于上次的误差 if (right < lastRight) { // 如果当前的误差小于之前最小的误差则更新 if (right < bestError) { // 最小的误差为当前误差 bestError = right; // 储存当前的一组权值 for (int noc = 0; noc < m_numClasses; noc++) { m_outputs[noc].saveWeights(); } // 初始化不更新权值的次数 driftOff = 0; } } // 不更新权值的次数加一 else { driftOff++; } // 最后一次更新的权值为当前权值 lastRight = right; // 如果权值超过了一定次数(初始20次)没有被更新或者实例已经达到被要求遍历的次数 if (driftOff > m_driftThreshold || noa + 1 >= m_numEpochs) { // 储存最终的权值 for (int noc = 0; noc < m_numClasses; noc++) { m_outputs[noc].restoreWeights(); } // 实例已经全部通过 m_accepted = true; } // 得到测试集的向后传播平均误差 right /= totalValWeight; } // 保存实际遍历实例集的次数 m_epoch = noa; // 保存向后传播错误率 m_error = right; } } // 自动生成隐含层的策略 private void setupHiddenLayer() { // 新建一个字符串拆分的实例 StringTokenizer tok = new StringTokenizer(m_hiddenLayers, ","); // 每层隐含层的节点数 int val = 0; // 上一层隐含层的节点数 int prev = 0; // 隐含层数 int num = tok.countTokens(); String c; // 对于每层隐含层 for (int noa = 0; noa < num; noa++) { // 得到该层的节点分布信息 c = tok.nextToken().trim(); // 如果是“a”代表的信息,该层的节点数为所有属性总数的一半(默认值) if (c.equals("a")) { val = (m_numAttributes + m_numClasses) / 2; } // 如果是“i”代表的信息,即输入单元,该层的节点数为所有非类属性个数 else if (c.equals("i")) { val = m_numAttributes; } // 如果是“o”代表的信息,即输出口,该层节点数为所有类属性个数 else if (c.equals("o")) { val = m_numClasses; } // 如果是“t”代表的信息,该层节点为所有属性总数 else if (c.equals("t")) { val = m_numAttributes + m_numClasses; } // 否则为分布信息为其他值,那么节点数为分布信息的字符串转化成浮点数再取整所得到的数为该层节点数 else { val = Double.valueOf(c).intValue(); } // 对于当前隐含层的每个节点 for (int nob = 0; nob < val; nob++) { // 新建节点 NeuralNode temp = new NeuralNode(String.valueOf(m_nextId), m_random,m_sigmoidUnit); // 节点编号增加 m_nextId++; // 计算节点坐标并添加节点 temp.setX(.5 / (num) * noa + .25); temp.setY((nob + 1.0) / (val + 1)); addNode(temp); // 如果不是第一层隐含层 if (noa > 0) { //将当前隐含层中的每个节点与上一层相连 for (int noc = m_neuralNodes.length - nob - 1 - prev; noc < m_neuralNodes.length - nob - 1; noc++) { NeuralConnection.connect(m_neuralNodes[noc], temp); } } } // 保存当前隐含层的节点数下次循环用 prev = val; } // 同上 tok = new StringTokenizer(m_hiddenLayers, ","); // 这次没有省去头和尾,也就是说包含了所有的输入单元和输出单元 c = tok.nextToken(); if (c.equals("a")) { val = (m_numAttributes + m_numClasses) / 2; } else if (c.equals("i")) { val = m_numAttributes; } else if (c.equals("o")) { val = m_numClasses; } else if (c.equals("t")) { val = m_numAttributes + m_numClasses; } else { val = Double.valueOf(c).intValue(); } // 如果没有隐含层 if (val == 0) { // 直接连接每个输入单元与每个输出单元 for (int noa = 0; noa < m_numAttributes; noa++) { for (int nob = 0; nob < m_numClasses; nob++) { NeuralConnection.connect(m_inputs[noa], m_neuralNodes[nob]); } } } // 如果有隐含层 else { // 就把输入单元与第一层隐含层的每个单元连接 for (int noa = 0; noa < m_numAttributes; noa++) { for (int nob = m_numClasses; nob < m_numClasses + val; nob++) { NeuralConnection.connect(m_inputs[noa], m_neuralNodes[nob]); } } // 把隐含层的最后一层的每个单元与输出单元连接 for (int noa = m_neuralNodes.length - prev; noa < m_neuralNodes.length; noa++) { for (int nob = 0; nob < m_numClasses; nob++) { NeuralConnection.connect(m_neuralNodes[noa], m_neuralNodes[nob]); } } } } // 计算某单元的输出值 public double outputValue(boolean calculate) { // 如果该单元之前没有被计算过且已经做好了计算的准备 if (Double.isNaN(m_unitValue) && calculate) { // 如果是输入单元 if (m_input) { // 如果没有与其他单元相连接,输出为0 if (m_currentInstance.isMissing(m_link)) { m_unitValue = 0; } // 如果与其他单元相连接,输出值为与之相连接的单元的值 else { m_unitValue = m_currentInstance.value(m_link); } } // 如果不是输出单元 else { // 初始化输出值 m_unitValue = 0; // 输出值为连接到此单元的输出的加权和 for (int noa = 0; noa < m_numInputs; noa++) { m_unitValue += m_inputList[noa].outputValue(true); } // 如果类属性是数值属性,且需要进行正常化,那么正常化到[-1,1]区间 if (m_numeric && m_normalizeClass) { m_unitValue = m_unitValue * m_attributeRanges[m_instances.classIndex()] + m_attributeBases[m_instances.classIndex()]; } } } return m_unitValue; }