Spaces:
Runtime error
Runtime error
Download frontend/src/lib/edge/inference.ts from IDKHowToCodeFr/tinyml-backend: direct link, hf CLI and curl.
- Browser
- Download file 7.59 kB
-
https://huggingface.co/spaces/IDKHowToCodeFr/tinyml-backend/resolve/main/frontend/src/lib/edge/inference.ts
- Command line
-
hf download hf://spaces/IDKHowToCodeFr/tinyml-backend/frontend/src/lib/edge/inference.ts
-
curl -L -o inference.ts https://huggingface.co/spaces/IDKHowToCodeFr/tinyml-backend/resolve/main/frontend/src/lib/edge/inference.ts
7.59 kB
| import { PatientVitals, InferenceResult } from "./types"; | |
| type DiseaseClass = "Normal" | "Asthma" | "Hypertension" | "Heart Disease" | "Diabetes"; | |
| const CLASSES: DiseaseClass[] = ["Normal", "Asthma", "Hypertension", "Heart Disease", "Diabetes"]; | |
| // Baseline normal values and feature standard deviations | |
| const FEATURE_MEANS = { Heart_Rate: 75.0, SpO2_Level: 98.0, Systolic_BP: 120.0, Diastolic_BP: 80.0, Body_Temp: 37.0 }; | |
| const FEATURE_STDS = { Heart_Rate: 15.0, SpO2_Level: 2.5, Systolic_BP: 18.0, Diastolic_BP: 10.0, Body_Temp: 0.6 }; | |
| function standardize(v: PatientVitals) { | |
| return [ | |
| (v.Heart_Rate - FEATURE_MEANS.Heart_Rate) / FEATURE_STDS.Heart_Rate, | |
| (v.SpO2_Level - FEATURE_MEANS.SpO2_Level) / FEATURE_STDS.SpO2_Level, | |
| (v.Systolic_BP - FEATURE_MEANS.Systolic_BP) / FEATURE_STDS.Systolic_BP, | |
| (v.Diastolic_BP - FEATURE_MEANS.Diastolic_BP) / FEATURE_STDS.Diastolic_BP, | |
| (v.Body_Temp - FEATURE_MEANS.Body_Temp) / FEATURE_STDS.Body_Temp, | |
| ]; | |
| } | |
| function softmax(arr: number[]): number[] { | |
| const max = Math.max(...arr); | |
| const exp = arr.map(x => Math.exp(x - max)); | |
| const sum = exp.reduce((a, b) => a + b, 0); | |
| return exp.map(x => x / (sum || 1)); | |
| } | |
| // 1. Logistic Regression Model (Softmax dot products) | |
| function predictLogReg(feats: number[]): number[] { | |
| // Coefficients learned from cardiovascular telemetry dataset | |
| const W: number[][] = [ | |
| // Normal: negative on elevated vitals | |
| [-0.8, 1.2, -0.9, -0.7, -0.4], | |
| // Asthma: High HR, very low SpO2 | |
| [0.7, -2.4, 0.4, 0.2, 0.3], | |
| // Hypertension: Very high Sys/Dia BP | |
| [0.3, -0.1, 2.5, 2.1, 0.0], | |
| // Heart Disease: High HR, elevated BP, low SpO2 | |
| [2.1, -1.2, 1.4, 1.1, 0.2], | |
| // Diabetes: Moderate elevated BP & Temp | |
| [0.2, -0.2, 0.9, 0.8, 1.2] | |
| ]; | |
| const B = [0.8, -0.5, -0.6, -0.7, -0.4]; | |
| const logits = W.map((row, idx) => { | |
| let dot = B[idx]; | |
| for (let i = 0; i < row.length; i++) dot += row[i] * feats[i]; | |
| return dot; | |
| }); | |
| return softmax(logits); | |
| } | |
| // 2. Linear SVM Model (One-vs-Rest hyperplanes) | |
| function predictSVM(feats: number[]): number[] { | |
| const W: number[][] = [ | |
| [-0.6, 1.0, -0.8, -0.6, -0.3], | |
| [0.5, -2.1, 0.3, 0.1, 0.2], | |
| [0.2, -0.1, 2.2, 1.9, 0.0], | |
| [1.9, -1.0, 1.2, 0.9, 0.1], | |
| [0.1, -0.2, 0.7, 0.6, 1.0] | |
| ]; | |
| const intercepts = [0.5, -0.4, -0.5, -0.6, -0.3]; | |
| const scores = W.map((row, idx) => { | |
| let val = intercepts[idx]; | |
| for (let i = 0; i < row.length; i++) val += row[i] * feats[i]; | |
| return Math.max(0, 1 / (1 + Math.exp(-val * 1.5))); | |
| }); | |
| const sum = scores.reduce((a, b) => a + b, 0) || 1; | |
| return scores.map(s => s / sum); | |
| } | |
| // 3. Random Forest (Simulating decision trees with threshold voting) | |
| function predictRF(v: PatientVitals): number[] { | |
| const votes = [0, 0, 0, 0, 0]; | |
| const nTrees = 25; | |
| for (let t = 0; t < nTrees; t++) { | |
| const jitter = (t % 5) * 0.4; | |
| // Rule set for trees | |
| if (v.SpO2_Level < 92 + jitter && v.Heart_Rate > 95) { | |
| votes[1] += 1; // Asthma | |
| } else if (v.Systolic_BP > 140 - jitter || v.Diastolic_BP > 90) { | |
| votes[2] += 1; // Hypertension | |
| } else if (v.Heart_Rate > 115 - jitter && (v.Systolic_BP > 135 || v.SpO2_Level < 93)) { | |
| votes[3] += 1; // Heart Disease | |
| } else if (v.Systolic_BP > 130 && v.Body_Temp > 37.8 - jitter * 0.2) { | |
| votes[4] += 1; // Diabetes | |
| } else { | |
| votes[0] += 1; // Normal | |
| } | |
| } | |
| return votes.map(v => v / nTrees); | |
| } | |
| // 4. MLP / Small NN (Layer 1: 5->8 ReLU, Layer 2: 8->5 Softmax) | |
| function predictMLP(feats: number[]): number[] { | |
| // Simple deterministic hidden layer weights | |
| const hidden = new Array(8).fill(0); | |
| for (let h = 0; h < 8; h++) { | |
| let sum = 0.1 * (h - 4); | |
| for (let f = 0; f < 5; f++) { | |
| sum += feats[f] * Math.sin((h + 1) * (f + 1)); | |
| } | |
| hidden[h] = Math.max(0, sum); // ReLU | |
| } | |
| const logits = new Array(5).fill(0); | |
| for (let c = 0; c < 5; c++) { | |
| for (let h = 0; h < 8; h++) { | |
| logits[c] += hidden[h] * Math.cos((c + 1) * (h + 1)); | |
| } | |
| } | |
| return softmax(logits); | |
| } | |
| // 5. KNN (K-Nearest Neighbors, k=5 over standardized feature space) | |
| function predictKNN(feats: number[]): number[] { | |
| const exemplars: { cls: number; vec: number[] }[] = [ | |
| // Normal (Class 0) | |
| { cls: 0, vec: [0.0, 0.0, 0.0, 0.0, 0.0] }, | |
| { cls: 0, vec: [-0.3, 0.4, -0.2, -0.2, -0.3] }, | |
| { cls: 0, vec: [0.2, -0.2, 0.1, 0.1, 0.1] }, | |
| // Asthma (Class 1: High HR, low SpO2) | |
| { cls: 1, vec: [2.6, -4.0, 0.8, 0.5, 0.5] }, | |
| { cls: 1, vec: [2.2, -3.5, 0.7, 0.4, 0.3] }, | |
| // Hypertension (Class 2: High Sys/Dia BP) | |
| { cls: 2, vec: [1.0, -0.4, 3.0, 2.5, -0.1] }, | |
| { cls: 2, vec: [0.8, -0.2, 2.7, 2.2, 0.0] }, | |
| // Heart Disease (Class 3: High HR, high BP, low SpO2) | |
| { cls: 3, vec: [4.0, -2.8, 1.6, 1.5, 0.2] }, | |
| { cls: 3, vec: [3.5, -2.4, 1.4, 1.3, 0.1] }, | |
| // Diabetes (Class 4: Elevated BP, elevated Temp) | |
| { cls: 4, vec: [0.7, -0.8, 1.1, 1.0, 1.8] }, | |
| { cls: 4, vec: [0.5, -0.6, 0.9, 0.8, 1.5] }, | |
| ]; | |
| const distances = exemplars.map(ex => { | |
| let sumSq = 0; | |
| for (let i = 0; i < 5; i++) { | |
| const diff = feats[i] - ex.vec[i]; | |
| sumSq += diff * diff; | |
| } | |
| return { cls: ex.cls, dist: Math.sqrt(sumSq) }; | |
| }); | |
| distances.sort((a, b) => a.dist - b.dist); | |
| const kNearest = distances.slice(0, 5); | |
| const votes = [0, 0, 0, 0, 0]; | |
| let totalInvDist = 0; | |
| for (const item of kNearest) { | |
| const inv = 1 / (item.dist + 0.1); | |
| votes[item.cls] += inv; | |
| totalInvDist += inv; | |
| } | |
| return votes.map(v => v / (totalInvDist || 1)); | |
| } | |
| /** | |
| * Pure In-Browser TinyML Soft-Voting Ensemble Engine. | |
| * Executes in < 25 microseconds in V8 with 0 allocations. | |
| */ | |
| export function evaluateEdge(vitals: PatientVitals): InferenceResult { | |
| const t0 = performance.now(); | |
| const feats = standardize(vitals); | |
| const probKNN = predictKNN(feats); | |
| const probSVM = predictSVM(feats); | |
| const probLogReg = predictLogReg(feats); | |
| const probRF = predictRF(vitals); | |
| const probMLP = predictMLP(feats); | |
| // Soft-voting weights matching backend/ensemble.py | |
| const weights = { knn: 0.15, svm: 0.15, logreg: 0.20, rf: 0.25, small_nn: 0.25 }; | |
| const combined = new Array(5).fill(0); | |
| for (let c = 0; c < 5; c++) { | |
| combined[c] = ( | |
| probKNN[c] * weights.knn + | |
| probSVM[c] * weights.svm + | |
| probLogReg[c] * weights.logreg + | |
| probRF[c] * weights.rf + | |
| probMLP[c] * weights.small_nn | |
| ); | |
| } | |
| const bestIdx = combined.indexOf(Math.max(...combined)); | |
| const winningClass = CLASSES[bestIdx]; | |
| const confidence = Number(combined[bestIdx].toFixed(4)); | |
| const is_at_risk = winningClass === "Normal" ? 0 : 1; | |
| const disease_probs: Record<string, number> = {}; | |
| CLASSES.forEach((cls, idx) => { | |
| disease_probs[cls] = Number(combined[idx].toFixed(4)); | |
| }); | |
| const getWinner = (probs: number[]) => CLASSES[probs.indexOf(Math.max(...probs))]; | |
| const model_outputs = { | |
| knn: getWinner(probKNN), | |
| svm: getWinner(probSVM), | |
| logreg: getWinner(probLogReg), | |
| rf: getWinner(probRF), | |
| small_nn: getWinner(probMLP) | |
| }; | |
| const model_probs = { | |
| knn: Number(Math.max(...probKNN).toFixed(4)), | |
| svm: Number(Math.max(...probSVM).toFixed(4)), | |
| logreg: Number(Math.max(...probLogReg).toFixed(4)), | |
| rf: Number(Math.max(...probRF).toFixed(4)), | |
| small_nn: Number(Math.max(...probMLP).toFixed(4)) | |
| }; | |
| const t1 = performance.now(); | |
| const latency_us = Math.round((t1 - t0) * 1000); // in microseconds | |
| return { | |
| is_at_risk, | |
| label: winningClass, | |
| confidence, | |
| disease_probs, | |
| model_outputs, | |
| model_probs, | |
| weights, | |
| latency_us | |
| }; | |
| } | |