UNPKG

@obliczeniowo/elementary

Version:
342 lines (336 loc) 16.6 kB
import * as i2 from '@obliczeniowo/elementary/array-to-table'; import { ArrayToTableModule } from '@obliczeniowo/elementary/array-to-table'; import * as i1 from '@obliczeniowo/elementary/linear-diagram'; import { PointType, LinearDiagramModule } from '@obliczeniowo/elementary/linear-diagram'; import * as i0 from '@angular/core'; import { EventEmitter, ViewChild, Output, Input, Component, NgModule } from '@angular/core'; import { CommonModule } from '@angular/common'; import { ElementaryMath } from '@obliczeniowo/elementary/math'; import { LinePattern } from '@obliczeniowo/elementary/drawing'; import { Point2D } from '@obliczeniowo/elementary/classes'; /** * Single perceptron model */ class Perceptron { paramsLength; eta; weights = []; error = 0; errors = []; constructor(paramsLength, eta) { this.paramsLength = paramsLength; this.eta = eta; this.setParamsLength(paramsLength); this.setEta(eta); } getFactor(parameters) { let factor = 0; for (let i = 0; i < this.paramsLength; i++) { factor += this.weights[i] * parameters[i]; } return factor; } getError() { return this.error; } getErrors() { return this.errors; } setParamsLength(paramsLength) { this.paramsLength = Math.ceil(paramsLength + 1); if (this.paramsLength < 1) { throw new Error('Number of parameters must be greater then 0'); } this.weights = new Array(this.paramsLength).fill(1); } setEta(eta) { if (eta <= 0) { throw new Error(`Eta should be > 0 but ${eta} was given`); } this.eta = eta; } /** * Output perceptron response * @param parameters input parameters * @returns -1 / 1 value */ y(parameters) { if (parameters.length === this.paramsLength - 1) { parameters = [...parameters, 1]; } const factor = this.getFactor(parameters); if (factor > 0) { return 1; } return -1; } /** * Add training data single record * @param parameters array of this.parameters length * @param y expected value -1 or 1 */ train(parameters, y) { if (parameters.length === this.paramsLength - 1) { parameters = [...parameters, 1]; } const error = y - this.y(parameters); this.error += Math.abs(error); const delta = error * this.eta; this.weights.forEach((_, index) => { this.weights[index] += delta * parameters[index]; }); } /** * Trening perceptron over the epoch times clear previously created table * @param x input parameters training table (size must be equal parameter field of this class) * @param y output expected value * @param epoch how many time repeat training session */ fit(x, y, epoch) { this.weights.fill(1); this.error = 0; this.errors = []; for (let i = 0; i < epoch; i++) { x.forEach((params, index) => this.train(params, y[index])); this.errors.push(this.error); this.error = 0; } } /** */ getWeights() { return this.weights; } /** */ setWeights(weights) { if (this.paramsLength !== weights.length) { throw new Error(`Try to set weights that have wrong length. Expected: ${this.paramsLength} but ${weights.length}`); } this.weights = [...weights]; } /** * Calc linear function factors for m dimension that is equal to number of input parameters * and weights inside perceptron * * @param n - determine index for f(xn) function of m dimension, n is integer from range 0 * to m - 1 * @returns table of factors for f(xn) function, to calculate xn of m dimension you need to * calc: * * xn = suma od k = 0 do k < n { f[k] * x[k] } + f[n] */ fXnFactors(n) { const factors = []; for (let i = 0; i < this.paramsLength; i++) { if (i !== n) { factors.push(-this.weights[i] / this.weights[n]); } } return factors; } } class PerceptronComponent { LinePattern = LinePattern; PointType = PointType; /** * Perceptron inputs table all of the same size must be * * this.x[k].length === this.inputs */ x = []; /** * Perceptron y outputs */ y = []; /** * Numbers of rounds */ epoch = 100; /** * x to predict y after learning session * * this.xWithoutY[k].length === this.inputs */ xWithoutY = []; /** * Expected input parameters, determine size of x second index table * * this.x[k].length === this.inputs */ inputs = 2; /** * Value determine speed of learning. Range: > 0 to 1 */ eta = 0.1; /** */ display = { description: true, diagram: true }; /** */ set weights(weights) { this.perceptron.setWeights(weights); if (this.xWithoutY) { this.predict(); } } /** */ get weights() { return this.perceptron.getWeights(); } /** * Emit predicted value */ predicted = new EventEmitter(); svg; yPred = []; points = []; xMinMax; yMinMax; perceptron = new Perceptron(this.inputs, this.eta); ngOnChanges(changes) { if (changes.inputs) { this.perceptron.setParamsLength(this.inputs); } if (changes.eta) { this.perceptron.setEta(this.eta); } if (changes.eta || changes.inputs || changes.epoch || changes.x || changes.y) { this.recalc(); } if (changes.xWithoutY) { this.predict(); } } getError() { return this.perceptron.getError(); } getErrors() { return this.perceptron.getErrors(); } getWeights() { return this.perceptron.getWeights(); } getWeightsHeaders() { return this.getWeights().map((_, index) => `W ${index}`); } getXHeaders() { return new Array(this.perceptron.getWeights().length - 1).fill(0).map((_, index) => `X ${index + 1}`); } predict() { const yPred = []; this.xWithoutY.forEach(x => yPred.push(this.perceptron.y(x))); this.yPred = yPred; this.predicted.emit(this.yPred); } recalc() { if (this.x.length === this.y.length) { this.perceptron.fit(this.x, this.y, this.epoch); if (this.xWithoutY) { this.predict(); } if (this.x?.length === this.y?.length) { this.points = []; if (this.inputs > 1) { const first = []; const second = []; const points = []; this.x.forEach((x, index) => { if (this.y[index] < 0) { first.push(new Point2D(x[0], x[1])); } else { second.push(new Point2D(x[0], x[1])); } }); points.push(first); points.push(second); if (this.xWithoutY.length && this.yPred) { const predictedFirst = []; const predictedSecond = []; this.xWithoutY.forEach((x, index) => { if (this.yPred[index] === -1) { predictedFirst.push(new Point2D(x[0], x[1])); } else { predictedSecond.push(new Point2D(x[0], x[1])); } }); points.push(predictedFirst); points.push(predictedSecond); } const all = points.reduce((p, c) => (p = p.concat(c), p), []); this.xMinMax = ElementaryMath.getMinMax(all.map(p => p.x)); this.yMinMax = ElementaryMath.getMinMax(all.map(p => p.y)); const params = this.perceptron.fXnFactors(1); const calc = (x) => params[0] * x + params[1]; points.push([ new Point2D(this.xMinMax.min, calc(this.xMinMax.min)), new Point2D(this.xMinMax.max, calc(this.xMinMax.max)) ]); this.points = points; const dX = ((this.xMinMax.max - this.xMinMax.min) * 0.01) || 1; const dY = ((this.yMinMax.max - this.yMinMax.min) * 0.01) || 1; this.xMinMax = { min: this.xMinMax.min - dX, max: this.xMinMax.max + dX }; this.yMinMax = { min: this.yMinMax.min - dY, max: this.yMinMax.max + dY }; } } } } static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.0.4", ngImport: i0, type: PerceptronComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.0.4", type: PerceptronComponent, isStandalone: false, selector: "obl-perceptron", inputs: { x: "x", y: "y", epoch: "epoch", xWithoutY: "xWithoutY", inputs: "inputs", eta: "eta", display: "display", weights: "weights" }, outputs: { predicted: "predicted" }, viewQueries: [{ propertyName: "svg", first: true, predicate: ["svg"], descendants: true }], usesOnChanges: true, ngImport: i0, template: "@if (display.description) {\n <div>\n <obl-array-to-table\n caption=\"Weights\"\n [maxLength]=\"4\"\n [display]=\"{ index: false, download: 'csv' }\"\n [array]=\"[getWeights()]\"\n [headers]=\"getWeightsHeaders()\"\n ></obl-array-to-table>\n </div>\n <div><b>\u03B7 (eta):</b> {{ eta }}</div>\n <div><b>Epoch:</b> {{ epoch }}</div>\n\n <div>\n <obl-array-to-table\n caption=\"X without y:\"\n [array]=\"xWithoutY\"\n [headers]=\"getXHeaders()\"\n ></obl-array-to-table>\n\n <obl-array-to-table\n caption=\"Y predicted\"\n [maxLength]=\"15\"\n [display]=\"{ index: false, download: 'csv' }\"\n [array]=\"[yPred]\"\n ></obl-array-to-table>\n </div>\n <obl-array-to-table\n caption=\"Errors\"\n [maxLength]=\"15\"\n [display]=\"{ index: false, download: 'csv' }\"\n [array]=\"[getErrors()]\"\n ></obl-array-to-table>\n}\n\n@if (display.diagram) {\n <obl-linear-diagram-2d\n [points]=\"points\"\n [options]=\"{\n xMinMax,\n yMinMax,\n set: [\n {\n color: '#ff0000',\n stroke: 0,\n linePattern: LinePattern.DISABLED,\n drawPoint: PointType.STAR\n },\n {\n color: '#0000ff',\n stroke: 2,\n linePattern: LinePattern.DISABLED,\n drawPoint: PointType.X\n },\n {\n color: '#ff8800',\n stroke: 0,\n linePattern: LinePattern.DISABLED,\n drawPoint: PointType.STAR\n },\n {\n color: '#0088ff',\n stroke: 2,\n linePattern: LinePattern.DISABLED,\n drawPoint: PointType.X\n },\n {\n color: '#00ff00',\n stroke: 2,\n linePattern: LinePattern.NONE,\n drawPoint: PointType.NONE\n },\n ]\n }\"\n [legend]=\"['Y = -1', 'Y = 1', 'Pred Y = -1', 'Pred Y = 1', 'border function']\"\n ></obl-linear-diagram-2d>\n}\n", styles: [":host{display:flex;flex-direction:column}:host div{padding:5px}:host .perceptron{display:flex;justify-content:center}obl-array-to-table{max-width:100%}\n"], dependencies: [{ kind: "component", type: i1.LinearDiagram2DComponent, selector: "obl-linear-diagram-2d", inputs: ["legend", "points", "xFormatter", "yFormatter", "options", "labels"] }, { kind: "component", type: i2.ArrayToTableComponent, selector: "obl-array-to-table", inputs: ["array", "headers", "caption", "maxLength", "editable", "display"], outputs: ["arrayChanged"] }] }); } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.0.4", ngImport: i0, type: PerceptronComponent, decorators: [{ type: Component, args: [{ selector: 'obl-perceptron', standalone: false, template: "@if (display.description) {\n <div>\n <obl-array-to-table\n caption=\"Weights\"\n [maxLength]=\"4\"\n [display]=\"{ index: false, download: 'csv' }\"\n [array]=\"[getWeights()]\"\n [headers]=\"getWeightsHeaders()\"\n ></obl-array-to-table>\n </div>\n <div><b>\u03B7 (eta):</b> {{ eta }}</div>\n <div><b>Epoch:</b> {{ epoch }}</div>\n\n <div>\n <obl-array-to-table\n caption=\"X without y:\"\n [array]=\"xWithoutY\"\n [headers]=\"getXHeaders()\"\n ></obl-array-to-table>\n\n <obl-array-to-table\n caption=\"Y predicted\"\n [maxLength]=\"15\"\n [display]=\"{ index: false, download: 'csv' }\"\n [array]=\"[yPred]\"\n ></obl-array-to-table>\n </div>\n <obl-array-to-table\n caption=\"Errors\"\n [maxLength]=\"15\"\n [display]=\"{ index: false, download: 'csv' }\"\n [array]=\"[getErrors()]\"\n ></obl-array-to-table>\n}\n\n@if (display.diagram) {\n <obl-linear-diagram-2d\n [points]=\"points\"\n [options]=\"{\n xMinMax,\n yMinMax,\n set: [\n {\n color: '#ff0000',\n stroke: 0,\n linePattern: LinePattern.DISABLED,\n drawPoint: PointType.STAR\n },\n {\n color: '#0000ff',\n stroke: 2,\n linePattern: LinePattern.DISABLED,\n drawPoint: PointType.X\n },\n {\n color: '#ff8800',\n stroke: 0,\n linePattern: LinePattern.DISABLED,\n drawPoint: PointType.STAR\n },\n {\n color: '#0088ff',\n stroke: 2,\n linePattern: LinePattern.DISABLED,\n drawPoint: PointType.X\n },\n {\n color: '#00ff00',\n stroke: 2,\n linePattern: LinePattern.NONE,\n drawPoint: PointType.NONE\n },\n ]\n }\"\n [legend]=\"['Y = -1', 'Y = 1', 'Pred Y = -1', 'Pred Y = 1', 'border function']\"\n ></obl-linear-diagram-2d>\n}\n", styles: [":host{display:flex;flex-direction:column}:host div{padding:5px}:host .perceptron{display:flex;justify-content:center}obl-array-to-table{max-width:100%}\n"] }] }], propDecorators: { x: [{ type: Input }], y: [{ type: Input }], epoch: [{ type: Input }], xWithoutY: [{ type: Input }], inputs: [{ type: Input }], eta: [{ type: Input }], display: [{ type: Input }], weights: [{ type: Input }], predicted: [{ type: Output }], svg: [{ type: ViewChild, args: ['svg'] }] } }); class PerceptronModule { static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.0.4", ngImport: i0, type: PerceptronModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule }); static ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "20.0.4", ngImport: i0, type: PerceptronModule, declarations: [PerceptronComponent], imports: [CommonModule, LinearDiagramModule, ArrayToTableModule], exports: [PerceptronComponent] }); static ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "20.0.4", ngImport: i0, type: PerceptronModule, imports: [CommonModule, LinearDiagramModule, ArrayToTableModule] }); } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.0.4", ngImport: i0, type: PerceptronModule, decorators: [{ type: NgModule, args: [{ declarations: [ PerceptronComponent ], imports: [ CommonModule, LinearDiagramModule, ArrayToTableModule ], exports: [ PerceptronComponent ] }] }] }); /** * Generated bundle index. Do not edit. */ export { Perceptron, PerceptronComponent, PerceptronModule }; //# sourceMappingURL=obliczeniowo-elementary-perceptron.mjs.map