Javascript 用普通JS编写的中性网络的奇怪行为

Javascript 用普通JS编写的中性网络的奇怪行为,javascript,node.js,typescript,neural-network,deep-learning,Javascript,Node.js,Typescript,Neural Network,Deep Learning,对神经网络的可能性印象深刻的是,我决定在使用任何库之前,我都想了解它们是如何工作的。所以我写了一个简单的训练应用程序,它使用3层网络,每个网络有2个神经元。有一块400×400的帆布。给定鼠标在画布上的x,y坐标,它应该给出结果坐标/400(因此对于100300,它应该给出0.25,0.75) 培训看起来很合理 但当我切换到预测模式时,网络对每个训练批始终给出相同的结果。无论输入是什么,它都会给出相同的结果。 经过更多的训练后,输出会发生变化,但对于每个输入来说都是一样的 这是用打字稿写的。

对神经网络的可能性印象深刻的是,我决定在使用任何库之前,我都想了解它们是如何工作的。所以我写了一个简单的训练应用程序,它使用3层网络,每个网络有2个神经元。有一块400×400的帆布。给定鼠标在画布上的x,y坐标,它应该给出结果坐标/400(因此对于100300,它应该给出0.25,0.75)

培训看起来很合理

但当我切换到预测模式时,网络对每个训练批始终给出相同的结果。无论输入是什么,它都会给出相同的结果。 经过更多的训练后,输出会发生变化,但对于每个输入来说都是一样的

这是用打字稿写的。 我没有粘贴整个web培训页面,而是制作了培训脚本,以便您可以更清楚地看到正在发生的事情

索引
let sigmoid:ActivationFunction={
func:(x:number)=>(1/(1+Math.exp(-x)),
导数:(z:数字)=>{
返回sigmoid.func(z)*(1-sigmoid.func(z));
}
};
从“./Matrix”导入矩阵;
类中性网络{
层:阵列;
权重:矩阵[];
偏差:矩阵[];
激活功能:激活功能;
学习率:数字;
构造函数(…层:数组){
这个。层=层;
此.u函数=sigmoid;
//使用随机权重和偏差初始化神经网络[-1;1]
此参数为:权重=[];
对于(设i=0;i0?输出[i-1]:矩阵.createFromArray(originalInput));
让Delta=矩阵。乘法(梯度,输出层在重新检查之前);
this.weights[i].add(delta);
此.bias[i].添加(渐变);
设weightsTransposed=Matrix.transpose(this.weights[i]);
误差=矩阵乘法(加权移位,误差);
}
返回输出[outputs.length-1].toArray();
}
setActivationFunction(activationFunction=sigmoid){
this.activation\u函数=activationFunction;
}
设置学习率(学习率=0.1){
这个。学习率=学习率;
}
}
接口激活功能{
func(x:编号):编号;
导数(x:数):数;
}
导出=中性网络;
培训
let NN=require('./索引');
设n=新的NN(2,2,2);
让data=generateTrainingData();
forEach(d=>n.train(d.i,d.o));
//检查培训情况
设指数=0
设t=setInterval(()=>{
设pred=n.前馈(数据[index].i);
console.log(`PREDICTED-${pred}EXPECTED=${data[index].o}COST-${Math.pow(pred[0]-data[index].o[0],2)+Math.pow(pred[1]-data[index].o[1],2)})
如果(索引+==1000)clearInterval(t);
}, 500);
函数generateTrainingData(){
让数据=[];
for(设i=0;i数组(this.columns).fill(0));
}
静态映射(矩阵,f):矩阵{
设m=新矩阵(矩阵.行,矩阵.列);
m、 map((v,i,j)=>f(矩阵数据[i][j],i,j));
返回m;
}
地图(f){
for(设i=0;iMath.random()*2-1);
}
加(n){
if(矩阵的n个实例){
if(this.rows!==n.rows | | this.columns!==n.columns){
抛出新错误('两个矩阵的大小必须匹配!');
}
返回这个.map((v,i,j)=>v+n.data[i][j]);
}否则{
返回这个.map(v=>v+n);
}
}
静态减法(a,b):矩阵{
if(a.行!==b.行| | a.列!==b.列){
抛出新错误('两个矩阵的大小必须匹配!');
}
设m=新矩阵(a.行,a.列);
m、 map((u,i,j)=>a.data[i][j]-b.data[i][j]);
返回m;
}
静态乘法(a,b){
如果(a.列!==b.行){
抛出新错误('a.columns!==b.rows');
}
设m=新矩阵(a.行,b.列)
m、 map((u,i,j)=>{
设和=0;
for(设k=0;kv*n.data[i][j]);
}否则{
返回这个.map(v=>v*n);
}
}
toArray(){
设arr=[];
for(设i=0;imatrix.data[j][i]);
返回m;
}
静态createFromArray(arr):矩阵{
设m=新矩阵(arr.length,1);
m、 map((v,i)=>arr[i]);
返回m;
}
}

我不确定是什么原因造成的。我已经试着调试这几天了,但我认为我缺乏经验并没有让我看到这里的问题。非常感谢您的帮助。

矩阵中有一个错误。乘法类方法。它应该是
a.columns
,而不是
a.cols
。因此,
渐变
增量
没有正确更新。

你完全正确,但仍然没有完全修复它。网络的行为仍然很奇怪,它使这些“跳跃”我不确定这是一个代码问题还是一个机器学习问题。您可能希望为每个矩阵方法创建单元测试,以使sur
let sigmoid: ActivationFunction = {
    func: (x: number) => (1 / (1 + Math.exp(-x))),
    derivative: (z: number) => {
        return sigmoid.func(z) * (1 - sigmoid.func(z));
    }
};

import Matrix from './matrix';

class NeutralNetwork {
    layers: Array<number>;
    weights: Matrix[];
    biases: Matrix[];
    activation_function: ActivationFunction;
    learning_rate: number;

    constructor(...layers: Array<number>) {
        this.layers = layers;
        this.activation_function = sigmoid;

        //Initialize neural network with random weigths and biases [-1;1]
        this.weights = [];
        for(let i=0; i<this.layers.length - 1; i++){
            this.weights.push(new Matrix(this.layers[i+1], this.layers[i]));
            this.weights[i].randomize();
        }
        this.biases = [];
        for(let i=1; i<this.layers.length; i++){
            this.biases.push(new Matrix(this.layers[i], 1));
            this.biases[i-1].randomize();
        }

        this.setActivationFunction();
        this.setLearningRate();
    }

    feedForward(originalInput: Array<number>): Array<number> {
        if(originalInput.length != this.layers[0]) throw new Error("corrupt input data");

        let input : Matrix = Matrix.createFromArray(originalInput);
        for(let i = 0; i < this.layers.length - 1; i++){
            let output = Matrix.multiply(this.weights[i], input);
            output.add(this.biases[i]);
            output.map(this.activation_function.func);
            input = output;
        }

        return input.toArray();
    }

    train(originalInput: Array<number>, originalTarget: Array<number>) {
        if(originalInput.length != this.layers[0]) throw new Error("corrupt training data");
        if(originalTarget.length != this.layers[this.layers.length - 1]) throw new Error("corrupt training data");

        let outputs : Matrix[] = [];
        let input : Matrix = Matrix.createFromArray(originalInput);
        for(let i = 0; i < this.layers.length - 1; i++){
            let output = Matrix.multiply(this.weights[i], input);
            output.add(this.biases[i]);
            output.map(this.activation_function.func);
            input = output;
            outputs.push(output);
        }

        let target = Matrix.createFromArray(originalTarget);
        let errors = Matrix.subtract(target, outputs[this.layers.length - 2]);


        for(let i = this.layers.length - 2; i>=0; i--){
            let gradients = Matrix.map(outputs[i], this.activation_function.derivative);
            gradients.multiply(errors);
            gradients.multiply(this.learning_rate);

            let outputsOfLayerBeforeTransposed = Matrix.transpose(i > 0 ? outputs[i-1] : Matrix.createFromArray(originalInput));
            let deltas = Matrix.multiply(gradients, outputsOfLayerBeforeTransposed);

            this.weights[i].add(deltas);
            this.biases[i].add(gradients);

            let weightsTransposed = Matrix.transpose(this.weights[i]);
            errors = Matrix.multiply(weightsTransposed, errors);
        }

        return outputs[outputs.length - 1].toArray();

    }



    setActivationFunction(activationFunction = sigmoid) {
        this.activation_function = activationFunction;
    }
    setLearningRate(learning_rate = 0.1) {
        this.learning_rate = learning_rate;
    }
}


interface ActivationFunction {
    func(x: number): number;
    derivative(x: number): number;
}

export = NeutralNetwork;
let NN = require('./index');

let n = new NN(2,2,2);

let data = generateTrainingData();
data.forEach(d => n.train(d.i, d.o));

//check how well is it trained
let index = 0
let t = setInterval(()=>{
    let pred = n.feedForward(data[index].i);
    console.log(`PREDICTED - ${pred} EXPECTED = ${data[index].o} COST - ${Math.pow(pred[0]-data[index].o[0],2)+Math.pow(pred[1]-data[index].o[1],2)}`)
    if(index++ == 1000) clearInterval(t);
}, 500);

function generateTrainingData(){
    let data = [];
    for(let i=0;i<1000;i++){
        let x = Math.floor(Math.random() * 400);
        let y = Math.floor(Math.random() * 400);
        data.push({
            i : [x,y],
            o : [x/400, y/400]
        })
    }

    return data;
}
export default class Matrix {
    rows;
    columns;
    data: Array<Array<number>>;

    constructor(rows, columns) {
        this.rows = rows;
        this.columns = columns;
        this.data = new Array(this.rows).fill().map(() => Array(this.columns).fill(0));
    }

    static map(matrix, f) : Matrix{
        let m = new Matrix(matrix.rows, matrix.columns);
        m.map((v,i,j) => f(matrix.data[i][j], i, j));
        return m;
    }

    map(f) {
        for (let i = 0; i < this.rows; i++) {
            for (let j = 0; j < this.columns; j++) {
                this.data[i][j] = f(this.data[i][j], i, j);
            }
        }
    }

    randomize() {
        this.map(() => Math.random() * 2 - 1);
    }

    add(n) {
        if (n instanceof Matrix) {
            if (this.rows !== n.rows || this.columns !== n.columns) {
                throw new Error('Size of both matrices must match!');
            }
            return this.map((v, i, j) => v + n.data[i][j]);
        } else {
            return this.map(v => v + n);
        }
    }



    static subtract(a, b) : Matrix{
        if (a.rows !== b.rows || a.columns !== b.columns) {
            throw new Error('Size of both matrices must match!');
        }

        let m = new Matrix(a.rows, a.columns);
        m.map((_, i, j) => a.data[i][j] - b.data[i][j]);
        return m;
    }

    static multiply(a, b) {

        if (a.columns !== b.rows) {
            throw new Error('a.columns !== b.rows');
        }

        let m = new Matrix(a.rows, b.columns)
        m.map((_, i, j) => {
            let sum = 0;
            for (let k = 0; k < a.cols; k++) {
                sum += a.data[i][k] * b.data[k][j];
            }
            return sum;
        });

        return m;
    }
    multiply(n) {
        if (n instanceof Matrix) {
            if (this.rows !== n.rows || this.columns !== n.columns) {
                throw new Error('Size of both matrices must match!');
            }
            return this.map((v, i, j) => v * n.data[i][j]);
        } else {
            return this.map(v => v * n);
        }
    }
    toArray() {
        let arr = [];
        for (let i = 0; i < this.rows; i++) {
            for (let j = 0; j < this.columns; j++) {
                arr.push(this.data[i][j]);
            }
        }
        return arr;
    }

    static transpose(matrix) : Matrix {
        let m = new Matrix(matrix.columns, matrix.rows)
        m.map((_, i, j) => matrix.data[j][i]);
        return m;
    }

    static createFromArray(arr): Matrix {
        let m = new Matrix(arr.length, 1);
        m.map((v, i) => arr[i]);
        return m;
    }
}