我实现了一个反向传播神经网络,并用我的数据对其进行了训练。数据在英语和南非荷兰语的句子之间交替。神经网络应该能够识别输入的语言。
网络的结构是27 * 16 * 2,输入层有26个输入,对应字母表的每个字母,另外还有一个偏置单元。
我的问题是,每当遇到新的训练样本时,误差就会剧烈地在相反的方向上波动。正如我提到的,训练样本是以交替的方式读取的(英语,南非荷兰语,英语…)。
我可以训练网络识别所有英语或所有南非荷兰语,但无法在同一轮中识别两种语言中的任何一种(两种都识别)。
下面的y轴是两个输出节点(英语和南非荷兰语)的输出信号误差,x轴是训练样本的数量。从某种意义上说,它确实按照我编程的方式在执行;当样本是英语时,它会调整权重以更好地识别英语。然而,这样做时,它会使网络在预测南非荷兰语时的表现变差。这就是误差在正负值之间波动的原因。
显然,这不是它应该工作的方式,但我被卡住了。
我觉得问题可能出在我对概念的理解上,但这里是相关的代码:
public void train() throws NumberFormatException, IOException{ // 训练准确度
double at = 0; // 迭代次数
int epoch = 0;
int tNum = 0;
for(; epoch < epochMax; epoch++){ // 从现有项目中的TestPackage包中读取股票文件
BufferedReader br = new BufferedReader(new InputStreamReader(this.getClass().
getResourceAsStream("/TrainingData/" + trainingData.getName())));
while ((line = br.readLine()) != null) {
Boolean classified = false;
tNum++;
// 设置正确的分类Tk
t[0] = Integer.parseInt(line.split("\t")[0]); // 南非荷兰语
t[1] = (t[0] == 0) ? 1 : 0; // 英语
// 将训练字符串转换为字符数组
char trainingLine[] = line.split("\t")[1].toLowerCase().toCharArray();
// 增加输入层z的idx,与字母在字母表中的位置匹配
// a == 0, b == 2, 等等.....
for(int l = 0; l < trainingLine.length; l++){
if((int)trainingLine[l] >= 97 && (int)trainingLine[l] <= 122)
z[(int)trainingLine[l] % 97]++;
}
/*System.out.println("Z " + Arrays.toString(z));
System.out.println();*/
// 缩放Z
for(int i = 0; i < z.length-1; i++){
z[i] = scale(z[i], 0, trainingLine.length, -Math.sqrt(3),Math.sqrt(3));
}
/*----------------------------------------------------------------
* 设置网络隐藏层
* 隐藏层的每个第i个单元 =
* 输入层的每个第i个单元
* 乘以权重矩阵ij的第i层的每个j*/
for(int j = 0; j < ij.length; j++){ // 3
double[] dotProduct = multiplyVectors(z, ij[j]);
y[j] = sumVector(dotProduct);
}
/*----------------------------------------------------------------
* 设置隐藏层激活
*/
for(int j = 0; j < y.length-1; j++){
y[j] = sigmoid(y[j], .3, .7);
}
/*----------------------------------------------------------------
* 设置网络输出层
* 隐藏层的每个第j个单元 =
* 输入层的每个第j个单元
* 乘以权重矩阵jk的第j层的每个k*/
for(int k = 0; k < jk.length; k++){ // 3
double[] dotProduct = multiplyVectors(y, jk[k]);
o[k] = sumVector(dotProduct);
}
/*----------------------------------------------------------------
* 设置输出层激活
*/
for(int k = 0; k < o.length; k++){
o[k] = sigmoid(o[k], .3, .7);
}
/*----------------------------------------------------------------
* 设置输出误差
* 对于每个训练样本,评估误差。
* 误差定义为(Tk - Ok)
* 正确的分类将导致零误差:
* (1 - 1) = 0
* (0 - 0) = 0
*/
for(int k = 0; k < o.length; k++){
oError[k] = t[k] - o[k];
}
/*----------------------------------------------------------------
* 设置训练准确度
* 如果误差为0,则1表示成功预测。
* 如果误差为1,则0表示未成功预测。
*/
if(quantize(o[0],.3, .7) == t[0] && quantize(o[1], .3, .7) == t[1]){
classified = true;
at += 1;
}
// 仅在分类错误时计算误差并更改权重
if(classified){
continue;
}
/*----------------------------------------------------------------
* 计算输出信号误差
* ok的误差 = -(tk - ok)(1 - ok)ok
*/
for(int k = 0; k < o.length; k++){
oError[k] = outputError(t[k], o[k]);
}
/*----------------------------------------------------------------
* 计算隐藏层信号误差
* */
// 术语(1-yk)yk扩展为yk - yk的平方
// 对于每个第k个输出单元,将其乘以
// 两个术语(1-yk)yk和jk[k]的总和点积
for(int j = 0; j < y.length; j++){
for(int k = 0; k < o.length; k++){
/*System.out.println(j+"-"+k);*/
yError[j] += oError[k] * jk[k][j] * (1 - y[j]) * y[j];
}
}
/*----------------------------------------------------------------
* 计算隐藏-JK-输出层的新权重
* */
for(int k = 0; k < o.length; k++){
for(int j = 0; j < y.length; j++){
djk[k][j] = (-1*learningRate)*oError[k]*y[j] + momentum*djk[k][j];
// 旧权重 = 它们自己 + 新增权重
jk[k][j] += djk[k][j];
}
}
/*----------------------------------------------------------------
* 计算输入-IJ-隐藏层的新权重
* */
for(int j = 0; j < y.length-1; j++){
for(int i = 0; i < z.length; i++){
dij[j][i] = (-1*learningRate)*yError[j]*z[i] + momentum*dij[j][i];
// 旧权重 = 它们自己 + 新增权重
ij[j][i] += dij[j][i];
}
}
}
}
// 准确度百分比
double at_prec = (at/tNum) * 100;
System.out.println("训练准确度: " + at_prec);
}
回答:
我同意评论中提到的,这个模型可能不是你分类问题的最佳选择,但如果你有兴趣尝试让它工作,我会告诉你我认为它为什么会振荡,以及我会如何尝试解决这个问题。
根据我对你的问题和评论的理解,我无法理解网络在这种情况下实际上“学到了”什么。你输入字母(这是句子中字母出现的次数吗?),然后强制它映射到一个输出。假设你现在只使用英语,英语对应于输出1。那么你“训练”它在一个句子上,为了论证起见,它选择字母“a”作为决定性输入,这是一个相当常见的字母。它设置网络权重,使得当它看到“a”时,输出为1,而所有其他字母输入都被加权降低,以至于它们不影响输出。可能不是那么黑白分明,但它可能在做非常相似的事情。现在,每次你输入另一个英语句子时,它只需要看到一个“a”就能给出正确的输出。对仅南非荷兰语作为零输出做同样的事情,它将“a”映射到零。因此,每次你在两种语言之间交替时,它完全重新分配权重…你没有在构建一个结构。误差的反向传播基本上总是固定值,因为没有正确或错误的程度,它要么是这样,要么是那样。所以我预期它会像你看到的那样振荡。
编辑:我认为这归结为使用字母的存在来分类语言类别,并期望两个极端输出之一,而不是关于定义语言的字母之间的关系的任何东西。
在概念层面上,我会有一个完整的预处理阶段来获取一些统计数据。想到的,我可能会计算(我不了解该语言):- 句子中字母“a”与“c”出现的比率- 句子中字母“d”与“p”出现的比率- 句子中单词的平均长度
对每种语言的50个句子执行此操作。一次性输入所有数据,并在整个数据集上进行训练(70%用于训练,15%用于验证,15%用于测试)。你不能每次只在一个值上训练网络(我认为你正在这样做?),它需要看到整个画面。现在你的输出不再那么黑白分明,它有灵活性可以映射到0到1之间的值,而不是每次都是绝对值。任何高于0.5的都是英语,低于0.5的都是南非荷兰语。从10个语言统计参数开始,隐藏层有5个神经元,输出层有1个神经元。