我在使用Eclipse进行机器学习课程的作业时遇到了问题。
我在main()
方法中有一个while循环,理论上应该输出一个名为totError
的变量。每一次循环时,totError
的值应该根据变化的参数进行计算而不同。然而,我无法找到代码中出错的地方,它总是显示相同的值。我是否错误地使用了静态变量和方法?
我已经在下面粘贴了.java和.txt文件(遗憾的是,.txt文件太大,所以我只包含了其中的一小部分,但我的数组维度是正确的)。谁能指导我正确的方向?
package nn;import java.io.File;import java.io.FileReader;import java.io.BufferedReader;//import java.io.PrintStream;import java.io.IOException;import java.lang.Math; public class learningBP { // 声明变量 static int NUM_INPUTS = 10; // 包括1个偏置 static int NUM_HIDDENS = 10; // 包括1个偏置 static int NUM_OUTPUTS = 1; static double LEARNING_RATE = 0.1; static double MOMENTUM = 0.1; static double TOT_ERROR_THRESHOLD = 0.05; static double SIGMOID_UB = 1; static double SIGMOID_LB = -1; static double [][] wgtIH = new double[NUM_INPUTS][NUM_HIDDENS]; static double [][] dltWgtIH = new double[NUM_INPUTS][NUM_HIDDENS]; static double [][] wgtHO = new double[NUM_HIDDENS][NUM_OUTPUTS]; static double [][] dltWgtHO = new double[NUM_HIDDENS][NUM_OUTPUTS]; static int NUM_STATES_ACTIONS; static String [][] strLUT = new String[4*4*4*3*4*4][2]; static double [][] arrayLUT = new double[strLUT.length][2]; static double [][] arrayNormLUT = new double[strLUT.length][2]; static double [] arrayErrors = new double[strLUT.length]; static double [] arrayOutputs = new double[strLUT.length]; static double [] arrayNormOutputs = new double[strLUT.length]; static double [][] valueInput = new double[strLUT.length][NUM_INPUTS]; static double [][] valueHidden = new double[strLUT.length][NUM_HIDDENS]; static double [] dltOutputs = new double[strLUT.length]; static double [][] dltHiddens = new double[strLUT.length][NUM_HIDDENS]; static double totError = 1; static int numEpochs = 0; public static void main(String[] args) { // 加载查找表 String fileName = "/Users/XXXXX/Desktop/LUT.txt"; try { load(fileName); } catch (IOException e) { e.printStackTrace(); } // 初始化神经网络权重 initializeWeights(); while (totError > TOT_ERROR_THRESHOLD) { // 前向传播 fwdFeed(); // 反向传播 bckPropagation(); // 计算总误差 totError = calcTotError(arrayErrors); numEpochs += 1; System.out.println("训练轮数: "+numEpochs); System.out.println(totError); } } public double outputFor(double[] X) { // TODO 自动生成的方法存根 return 0; } public double train(double[] X, double argValue) { // TODO 自动生成的方法存根 return 0; } public void save(File argFile) { // TODO 自动生成的方法存根 } public static void load(String argFileName) throws IOException { // 从第二部分加载查找表训练集 BufferedReader r = new BufferedReader(new FileReader(new File(argFileName))); String l = r.readLine(); try { int a = 0; while (l != null) { String spt[] = l.split(" "); strLUT[a][0] = spt[0]; strLUT[a][1] = spt[1]; arrayLUT[a][0] = Double.parseDouble(strLUT[a][0]); arrayLUT[a][1] = Double.parseDouble(strLUT[a][1]); a += 1; l = r.readLine(); } } catch (IOException e) { e.printStackTrace(); } finally { r.close(); } // 将查找表标准化为双极性 for (int b = 0; b < arrayLUT.length; b++) { arrayNormLUT[b][0] = arrayLUT[b][0]; arrayNormLUT[b][1] = sigmoid(arrayLUT[b][1]); } } public static double sigmoid(double x) { // 双极性Sigmoid函数 return (SIGMOID_UB - SIGMOID_LB) / (1 + Math.pow(Math.E, -x)) + SIGMOID_LB; } public static void initializeWeights() { // 初始化输入层到隐藏层的权重 for (int i = 0; i < NUM_INPUTS; i++) { for (int j = 0; j < NUM_HIDDENS; j++) { wgtIH[i][j] = Math.random() - 0.5; dltWgtIH[i][j] = 0; } } // 初始化隐藏层到输出层的权重 for (int j = 0; j < NUM_HIDDENS; j++) { for (int k = 0; k < NUM_OUTPUTS; k++) { wgtHO[j][k] = Math.random() - 0.5; dltWgtHO[j][k] = 0; } } } public void zeroWeights() { // TODO 自动生成的方法存根 } public static void fwdFeed() { for(int z = 0; z < arrayLUT.length; z++) { // 标准化到[-1, 1]之间 valueInput[z][0] = (Character.getNumericValue(strLUT[z][0].charAt(0)) - 2.5)/1.5; // myX valueInput[z][1] = (Character.getNumericValue(strLUT[z][0].charAt(1)) - 2.5)/1.5; // myY valueInput[z][2] = (Character.getNumericValue(strLUT[z][0].charAt(2)) - 2.5)/1.5; // myHead valueInput[z][3] = Character.getNumericValue(strLUT[z][0].charAt(3)) - 2; // enProx valueInput[z][4] = (Character.getNumericValue(strLUT[z][0].charAt(4)) - 2.5)/1.5; // enAngle // 将四个可能的动作向量化为二进制 valueInput[z][5] = 0; valueInput[z][6] = 0; valueInput[z][7] = 0; valueInput[z][8] = 0; int action = Character.getNumericValue(strLUT[z][0].charAt(5)); // action valueInput[z][action-1] = 1; // 应用偏置输入 valueInput[z][9] = 1; // 计算隐藏神经元j的值 for(int j = 0; j < NUM_HIDDENS-1; j++) { valueHidden[z][j] = 0; for(int i = 0; i < NUM_INPUTS; i++) { valueHidden[z][j] += valueInput[z][i]*wgtIH[i][j]; } valueHidden[z][j] = sigmoid(valueHidden[z][j]); } // 应用偏置隐藏神经元 valueHidden[z][9] = 1; // 计算输出神经元的值 arrayOutputs[z] = 0; for(int j = 0; j < NUM_HIDDENS; j++) { arrayOutputs[z] += valueHidden[z][j]*wgtHO[j][0]; } arrayNormOutputs[z] = sigmoid(arrayOutputs[z]); arrayErrors[z] = arrayNormOutputs[z] - arrayNormLUT[z][1]; } } public static void bckPropagation() { for(int z = 0; z < arrayLUT.length; z++) { // 用于双极性Sigmoid的Delta规则 dltOutputs[z] = arrayErrors[z] * (1/2) * (1 + arrayNormLUT[z][1]) * (1 - arrayNormLUT[z][1]); // 计算隐藏层与输出层之间的权重更新 for(int j = 0; j < NUM_HIDDENS; j++) { dltWgtHO[j][0] = (LEARNING_RATE * dltOutputs[z] * valueHidden[z][j]) + (MOMENTUM * dltWgtHO[j][0]); wgtHO[j][0] += dltWgtHO[j][0]; } // 用于双极性Sigmoid的Delta规则 for(int j = 0; j < NUM_HIDDENS-1; j++) { dltHiddens[z][j] = (dltOutputs[z] * wgtHO[j][0]) * (1/2) * (1 + valueHidden[z][j]) * (1 - valueHidden[z][j]); // 计算输入层与隐藏层之间的权重更新 for(int i = 0; i < NUM_INPUTS; i++){ dltWgtIH[i][j] = (LEARNING_RATE * dltHiddens[z][j] * valueInput[z][i]) + (MOMENTUM * dltWgtIH[i][j]); wgtIH[i][j] += dltWgtIH[i][j]; } } } } public static double calcTotError(double [] Ar) { // 获取总误差 double outputTotError = 0; for(int z = 0; z < Ar.length; z++) { outputTotError += Math.pow(Ar[z], 2); } return outputTotError /= 2; } }
LUT.txt111111 10.941118079589064111112 -0.1111113 0.5562004990848579111114 1.98907128902595111121 11.862151157526291111122 0111123 -0.38423559443128236111124 0.2924429882372822111131 0111132 0111133 0111134 0.12275095886294243111141 -0.0545618032237386111142 1.111149754536815111143 -0.6483940696098076111144 -0.30397004441336395111211 8.104946515845224111212 3.4679914863334447111213 3.662003985119952111214 6.277685676457839111221 12.552710546022281111222 -0.09099267948190845111223 -0.29566545023952967111224 3.1487890500927063111231 0111232 0111233 0111234 8.934912143040652E-4111241 3.895126725032672111242 -0.2010016212971984111243 0.837429543536912111244 -0.27663053491694656111311 11.653951513990371111312 -0.2946973145089208111313 -0.2978184448888472111314 0.8279393778791164111321 0111322 0111323 0111324 2.2641633761201114111331 0111332 0111333 0111334 0111341 0111342 0111343 1.1732725059583249111344 -0.1112111 5.5359038859179535112112 0112113 0112114 0.0112121 0.08659995226070327112122 0.2798072139553114112123 5.49078110134232112124 -0.3108745952024568112131 -0.05965237074923033112132 0.09253924707369854112133 -0.4112134 3.161972099002618112141 -0.5260766570034812112142 -0.48090118837156254112143 -0.7310822755532788112144 3.486617439631581112211 0112212 0112213 0112214 0.6522588119326032112221 0112222 0112223 0112224 0.7460303984847749112231 0.23736484529821295112232 0.4052788544857546112233 0112234 2.951631100344372112241 0.5653655679375406112242 0.4971810465334616112243 7.402004693866543112244 -0.30000000000000004112311 0112312 0112313 0112314 0112321 0112322 0112323 0112324 0112331 0112332 0112333 0.4151878259768604112334 1.7724104736042405112341 0112342 0112343 4.069896885464749112344 -0.4113111 0113112 0.022566986598282823113113 0.08724320758208144113114 10.05432214824662113121 1.0564414035161591113122 -0.29029602924153364113123 -0.5541038225131591113124 8.672324872378988113131 -0.3654234566003739113132 -0.4113133 0.5004192669349199113134 2.078082532119674113141 0113142 0113143 0113144 1.2525107221533354113211 0113212 0.29495695502888564113213 -0.07529481401595756113214 -0.2404514421514272113221 -0.30000000000000004113222 0.7445615195514395113223 -0.3658317755666047113224 8.553656940756902113231 -0.30000000000000004113232 4.6010557496650915113233 -0.3879385840465742113234 -0.2113241 0.4326819938548774113242 0113243 0113244 1.1942595427121407113311 0113312 0113313 0.0113314 -0.30000000000000004113321 -0.30000000000000004113322 0113323 0113324 0.12628436039474933113331 0113332 0113333 0113334 1.1990757358685409113341 0113342 0113343 0113344 0114111 -0.2620854057619084114112 4.125854638322618114113 -0.6357408602214762114114 -0.3833440478188098114121 4.151592198100268114122 0.07881020285589568114123 0.2470962266586317114124 -0.614351130314123114131 0114132 0114133 0.137166408235687114134 -0.0736602283383406114141 0114142 1.79455706235276114143 -0.10778180504389967114144 -0.1095114211 4.093099235361004114212 0.43773368515345285114213 -0.22722143170688813114214 -0.47254408375084955114221 0.9666070656021031114222 5.3257648197212175114223 0.8550257571983391114224 1.7294133618581196114231 0114232 0114233 0.21693098965929433114234 -0.20056649258727272114241 0114242 0114243 -0.00420789076454664114244 -0.03980396617148699114311 -0.14894661319071242114312 2.8318004984996086114313 0.09972003835421428114314 -0.30000000000000004114321 -0.22014771207852618114322 3.6613263848490236114323 -0.961642132911289114324 -0.37587629822526014114331 0114332 0114333 0114334 0114341 0114342 0114343 0114344 0.01029174912920401121111 8.749283150544025121112 5.160303436301445121113 5.492968882659686121114 5.1300005456187545121121 9.080296371003485121122 5.48452094178394121123 8.364785563964707121124 8.988905334385453121131 0121132 0121133 3.9657653202217764121134 -0.1121141 4.299714795485242121142 -0.20100940661896582121143 -0.14475899994010905121144 0.7735726092109716121211 8.925285927651668121212 7.242378809714628121213 5.825241551756816121214 7.113455264749147121221 10.957172410507585121222 7.914499954045615121223 8.43670507913828121224 9.483271725903045121231 0121232 0121233 0121234 1.0618679323154605121241 3.916743510589585121242 -0.30816983215504323121243 0.18644548962100688121244 -0.05704324546134821121311 9.89354840660501121312 4.1887499584046495121313 8.597262669988885121314 4.6709783035857715121321 0.8690772369609352121322 0121323 0.0770114005696081121324 9.316545509588495121331 0121332 0121333 0121334 0121341 0.0017093447236721923121342 0.303857609787908121343 -0.09889618686732593121344 -0.1
回答:
我发现了以下技术和算法/数学上的缺陷:
技术问题:
将(1/2)
替换为0.5
,因为(1/2)
在Java中会导致0
(在Java中,除数或被除数或两者都必须是double类型,结果才为double类型,否则为int类型)。在bckPropagation()
方法中共有两个这样的实例。
数学问题1:
考虑到Delta规则(例如,http://users.pja.edu.pl/~msyd/wyk-nai/multiLayerNN-en.pdf)以及带动量的Delta规则(例如,http://ecee.colorado.edu/~ecen4831/lectures/deltasum.html),关于dltOutputs[z]
似乎存在符号错误。请在bckPropagation()
方法中将
dltOutputs[z] = arrayErrors[z] * (1/2) * (1 + arrayNormLUT[z][1]) * (1 - arrayNormLUT[z][1]);
替换为
dltOutputs[z] = -arrayErrors[z] * 0.5 * (1 + arrayNormLUT[z][1]) * (1 - arrayNormLUT[z][1]);
数学问题2(这里我不是很确定,但我想这是一个错误):测试案例z的权重只能依赖于当前轮次及之前所有轮次中测试案例z的数据(由于while循环)。目前,在bckPropagation()
方法中,测试案例z的权重还包含了当前轮次及之前所有轮次中所有之前的测试案例z’ < z的权重(由于for循环)。一个可能的解决方案是引入z作为权重的第三维度:wgtIH[z][i][j]
和wgtHO[z][j][0]
。现在,对权重的贡献对于每个测试案例z与其他测试案例z’是隔离的。为了考虑这一点,需要进行以下修改:
1) 定义:
static double [][][] wgtIH = new double[strLUT.length][NUM_INPUTS][NUM_HIDDENS];static double [][][] wgtHO = new double[strLUT.length][NUM_HIDDENS][NUM_OUTPUTS];
2) 初始化:
public static void initializeWeights() { for(int z = 0; z < arrayLUT.length; z++) { // 初始化输入层到隐藏层的权重 double rndWgtIH = Math.random() - 0.5; for (int i = 0; i < NUM_INPUTS; i++) { for (int j = 0; j < NUM_HIDDENS; j++) { wgtIH[z][i][j] = rndWgtIH; dltWgtIH[i][j] = 0; } } // 初始化隐藏层到输出层的权重 double rndWgtHO = Math.random() - 0.5; for (int j = 0; j < NUM_HIDDENS; j++) { for (int k = 0; k < NUM_OUTPUTS; k++) { wgtHO[z][j][k] = rndWgtHO; dltWgtHO[j][k] = 0; } } }}
3) fwdFeed()
和bckPropagation()
方法:
在这两个方法中,wgtIH[i][j]
和wgtHO[j][k]
需要分别替换为wgtIH[z][i][j]
和wgtHO[z][j][k]
。
示例:总误差随轮次数的变化
LEARNING_RATE = 0.4, MOMENTUM = 0.4, TOT_ERROR_THRESHOLD = 1; 训练轮数: 1 178.54336668545102 训练轮数: 10000 15.159692746944888 训练轮数: 20000 10.653887138186896 训练轮数: 30000 8.669183516487523 训练轮数: 40000 7.504963842773336 训练轮数: 50000 6.723327476195474 训练轮数: 60000 6.153237046947662 训练轮数: 70000 5.7133602902880325 训练轮数: 80000 5.360053126719502 训练轮数: 90000 5.06774284345891 训练轮数: 100000 4.820373442353342 训练轮数: 200000 3.4647965464740746 训练轮数: 300000 2.8350276017589153 训练轮数: 400000 2.4398876881673557 训练轮数: 500000 2.158533606426507 训练轮数: 600000 1.9432229058177424 训练轮数: 700000 1.770444540122524 训练轮数: 800000 1.627115257304848 训练轮数: 900000 1.5053344819279666 训练轮数: 1000000 1.4000233082047084 训练轮数: 1100000 1.3077427523972092 训练轮数: 1200000 1.2260577251537967 训练轮数: 1300000 1.153175740062673 训练轮数: 1400000 1.0877325511159377 训练轮数: 1500000 1.0286600703077815 持续时间: 822.8203290160001秒 -> 大约14分钟
如预期的那样,由于神经网络的学习进度,总误差在每个轮次中逐渐减少。