UNPKG

gradiatorjs

Version:

GradiatorJS is a lightweight, from-scratch autodiff engine and a neural network library written in typescript. Featuring a powerful automatic differentiation engine using a computation graph to enable backpropagation on dynamic network architectures. You

103 lines (90 loc) 3.92 kB
import { Val } from './val.js'; import * as op from './ops.js' export function meanSquaredErrorLoss(y_pred: Val, y_true: Val): Val { return op.div(op.sum(op.pow(op.sub(y_pred, y_true), 2)), y_true.size); } export function crossEntropyLoss_binary(y_pred: Val, y_true: Val, e: number = 1e-9): Val { if (y_pred.shape.join(',') !== y_true.shape.join(',')) { throw new Error(`Shape mismatch for BCE Loss: pred ${y_pred.shape}, true ${y_true.shape}`); } const batch_size = y_true.shape[0] || 1; const t1 = op.add(y_pred, e); const t2 = op.add(op.sub(1, y_pred), e); // cost = -1/m * sum(Y*log(A) + (1-Y)*log(1-A)) let total_sum = op.sum(op.add( op.mul(y_true, op.log(t1)), op.mul(op.sub(1, y_true), op.log(t2)) )); if (batch_size === 0) return new Val([], 0); let avg_loss = op.mul(-1/batch_size, total_sum) return avg_loss; } export function crossEntropyLoss_categorical(y_pred: Val, y_true: Val): Val { if (y_pred.shape.join(',') !== y_true.shape.join(',')) { throw new Error(`Shape mismatch for Cross-Entropy Loss. Pred: [${y_pred.shape}], True: [${y_true.shape}]`); } const epsilon = 1e-9; const log_pred = op.log(op.add(y_pred, epsilon)); const product = op.mul(y_true, log_pred); const negatedSum = op.mul(op.sum(product,1), -1); return op.mean(negatedSum); } /** * A combined softmax and categorical cross-entropy loss function. This is done to avoid passing the * backprop through a log fn. if the softmax outputs a probability P that is extremely close to zero, * log(P) will be -infinity and grad (1/x) will explode. * Backprop (dL/dZ) here will be just: predicted_probabilities - true_labels * @param logits raw, unnormalized output scores from the final dense layer (this is before softmax activation). [batch, numClasses] * @param y_true true labels, one-hot encoded. [batch, numClasses] */ export function crossEntropyLoss_softmax(logits: Val, y_true: Val): Val { if(logits.dim !== 2 || y_true.dim !== 2 || logits.shape.join(',') !== y_true.shape.join(',')) { throw new Error(`Shape mismatch for softmax `) } const batchSize = logits.shape[0]!; const numClasses = logits.shape[1]!; // applying softmax to get probs (P) const probs = new Val([batchSize, numClasses]); for (let b=0; b<batchSize; b++) { const rawOffset = b*numClasses; let max_logit = -Infinity; for (let j=0; j<numClasses; j++) { if (logits.data[rawOffset+j]>max_logit) { max_logit = logits.data[rawOffset+j]; } } let sum_exps = 0; for (let j=0; j<numClasses; j++) { const exp_val = Math.exp(logits.data[rawOffset+j]-max_logit); probs.data[rawOffset+j] = exp_val; sum_exps += exp_val; } for (let j=0; j<numClasses; j++) { probs.data[rawOffset+j] /= sum_exps; } } // cross-entropy here const epsilon = 1e-9; let totalLoss = 0; for (let i=0; i<y_true.data.length; i++) { if (y_true.data[i]===1.0) { totalLoss += -Math.log(probs.data[i]+epsilon); } } const avgLoss = totalLoss/batchSize; const lossVal = new Val([], avgLoss); lossVal._prev = new Set([logits, y_true]); lossVal._backward = ()=> { const dL_dLogits = new Float64Array(logits.size); for (let i=0; i<logits.size; i++) { dL_dLogits[i] = (probs.data[i] - y_true.data[i])/batchSize; } if (!logits.grad || logits.grad.length !== logits.size) { logits.grad = new Float64Array(logits.size).fill(0); } for (let i=0; i<logits.size; i++) { logits.grad[i]! += dL_dLogits[i]!; } }; return lossVal; }