class LocalClassifier { constructor() { this.weights = new Map(); // tag -> weight vector this.biases = new Map(); // tag -> bias this.learningRate = 0.01; this.featureDim = 512; // CLAP embedding dimension this.isInitialized = false; } initialize(featureDim = 512) { this.featureDim = featureDim; this.isInitialized = true; } // Simple logistic regression training trainOnFeedback(features, tag, feedback) { if (!this.isInitialized) { this.initialize(); } // Convert feedback to target value let target; switch (feedback) { case 'positive': target = 1.0; break; case 'negative': target = 0.0; break; case 'custom': target = 1.0; break; default: return; // Skip unknown feedback } // Initialize weights for new tag if (!this.weights.has(tag)) { this.weights.set(tag, new Array(this.featureDim).fill(0).map(() => (Math.random() - 0.5) * 0.01 )); this.biases.set(tag, 0); } const weights = this.weights.get(tag); const bias = this.biases.get(tag); // Forward pass let logit = bias; for (let i = 0; i < features.length; i++) { logit += weights[i] * features[i]; } // Sigmoid activation const prediction = 1 / (1 + Math.exp(-logit)); // Compute gradient const error = prediction - target; // Update weights and bias for (let i = 0; i < features.length; i++) { weights[i] -= this.learningRate * error * features[i]; } this.biases.set(tag, bias - this.learningRate * error); // Store updated weights this.weights.set(tag, weights); } // Predict confidence for a tag given features predict(features, tag) { if (!this.weights.has(tag)) { return null; // No training data for this tag } const weights = this.weights.get(tag); const bias = this.biases.get(tag); let logit = bias; for (let i = 0; i < Math.min(features.length, weights.length); i++) { logit += weights[i] * features[i]; } // Sigmoid activation return 1 / (1 + Math.exp(-logit)); } // Get all predictions for given features predictAll(features, candidateTags) { const predictions = []; for (const tag of candidateTags) { const confidence = this.predict(features, tag); if (confidence !== null) { predictions.push({ tag, confidence }); } } return predictions.sort((a, b) => b.confidence - a.confidence); } // Retrain on batch of feedback data retrainOnBatch(feedbackData) { for (const item of feedbackData) { if (item.audioFeatures && item.correctedTags) { // Create simple features from audio metadata const features = this.extractSimpleFeatures(item.audioFeatures); // Train on corrected tags for (const tagData of item.correctedTags) { this.trainOnFeedback(features, tagData.tag, tagData.feedback); } } } } // Extract simple features from audio metadata extractSimpleFeatures(audioFeatures) { // Create a simple feature vector from audio metadata // In a real implementation, this would use actual CLAP embeddings const features = new Array(this.featureDim).fill(0); if (audioFeatures) { // Use basic audio properties to create pseudo-features features[0] = audioFeatures.duration / 60; // Duration in minutes features[1] = audioFeatures.sampleRate / 48000; // Normalized sample rate features[2] = audioFeatures.numberOfChannels; // Number of channels // Fill remaining with small random values based on hash of properties const seed = this.simpleHash(JSON.stringify(audioFeatures)); for (let i = 3; i < this.featureDim; i++) { features[i] = this.seededRandom(seed + i) * 0.1; } } return features; } // Simple hash function for seeded random simpleHash(str) { let hash = 0; for (let i = 0; i < str.length; i++) { const char = str.charCodeAt(i); hash = ((hash << 5) - hash) + char; hash = hash & hash; // Convert to 32-bit integer } return Math.abs(hash); } // Seeded random number generator seededRandom(seed) { const x = Math.sin(seed) * 10000; return x - Math.floor(x); } // Save model to localStorage saveModel() { const modelData = { weights: Object.fromEntries(this.weights), biases: Object.fromEntries(this.biases), featureDim: this.featureDim, learningRate: this.learningRate }; localStorage.setItem('clipTaggerModel', JSON.stringify(modelData)); } // Load model from localStorage loadModel() { const saved = localStorage.getItem('clipTaggerModel'); if (saved) { try { const modelData = JSON.parse(saved); this.weights = new Map(Object.entries(modelData.weights)); this.biases = new Map(Object.entries(modelData.biases)); this.featureDim = modelData.featureDim || 512; this.learningRate = modelData.learningRate || 0.01; this.isInitialized = true; return true; } catch (error) { console.error('Error loading model:', error); } } return false; } // Get model statistics getModelStats() { return { trainedTags: this.weights.size, featureDim: this.featureDim, learningRate: this.learningRate, tags: Array.from(this.weights.keys()) }; } // Clear the model clearModel() { this.weights.clear(); this.biases.clear(); localStorage.removeItem('clipTaggerModel'); } } export default LocalClassifier;