@obliczeniowo/elementary
Version:
Library made in Angular version 20
342 lines (336 loc) • 16.6 kB
JavaScript
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