Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions backend/src/analysis/reactionAnalyzer.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { getReactionsForAnalysis } from "../db/operations/reactionOperations";
import { cosineSimilarityMatrix, computeAllSums } from "../utils/math";
import { normalizedCosineSimilarityMatrix, computeAllSums } from "../utils/math";

export interface ReactionAnalysis {
userIndexMap: Map<string, number>;
Expand Down Expand Up @@ -41,7 +41,7 @@ export async function analyzeReactions(graphId: string): Promise<ReactionAnalysi
}
}

const userSimilarityMatrix: number[][] = cosineSimilarityMatrix(votingMatrix);
const userSimilarityMatrix: number[][] = normalizedCosineSimilarityMatrix(votingMatrix);
const { sum_pos_pos, sum_pos_neg, sum_neg_pos, sum_neg_neg } = computeAllSums(userSimilarityMatrix, votingMatrix);
const uniquenessMatrix = votingMatrix.map((row, i) => row.map((value, j) => {
const sumIngroupAgree = sum_pos_pos[i][j];
Expand Down
105 changes: 69 additions & 36 deletions backend/src/utils/math.ts
Original file line number Diff line number Diff line change
@@ -1,50 +1,83 @@
import * as tf from '@tensorflow/tfjs-node';

export function cosineSimilarity(vector1: number[], vector2: number[]) {
if (vector1.length !== vector2.length) {
throw new Error('Embeddings must have the same length');
}

// Convert arrays to tensors
const tensor1 = tf.tensor1d(vector1);
const tensor2 = tf.tensor1d(vector2);

// Calculate dot product and magnitudes using TensorFlow
const dotProduct = tf.dot(tensor1, tensor2).dataSync()[0];
const magnitude1 = tf.norm(tensor1).dataSync()[0];
const magnitude2 = tf.norm(tensor2).dataSync()[0];

// Dispose tensors to free memory
tensor1.dispose();
tensor2.dispose();

return dotProduct / (magnitude1 * magnitude2);
if (vector1.length !== vector2.length) {
throw new Error('Embeddings must have the same length');
}

// Convert arrays to tensors
const tensor1 = tf.tensor1d(vector1);
const tensor2 = tf.tensor1d(vector2);

// Calculate dot product and magnitudes using TensorFlow
const dotProduct = tf.dot(tensor1, tensor2).dataSync()[0];
const magnitude1 = tf.norm(tensor1).dataSync()[0];
const magnitude2 = tf.norm(tensor2).dataSync()[0];

// Dispose tensors to free memory
tensor1.dispose();
tensor2.dispose();

return dotProduct / (magnitude1 * magnitude2);
}

export function cosineSimilarityMatrix(matrix: number[][]) {
if (matrix.length === 0 || matrix[0].length === 0) {
return [[]];
}
const matrixTensor = tf.tensor2d(matrix);
const dotProduct = tf.matMul(matrixTensor, matrixTensor.transpose());
if (matrix.length === 0 || matrix[0].length === 0) {
return [[]];
}
const matrixTensor = tf.tensor2d(matrix);
const dotProduct = tf.matMul(matrixTensor, matrixTensor.transpose());

const magnitude = tf.sqrt(tf.sum(tf.square(matrixTensor), 1, true));
const magnitudeProduct = tf.matMul(magnitude, magnitude.transpose());

const similarity = dotProduct.div(magnitudeProduct);
const magnitude = tf.sqrt(tf.sum(tf.square(matrixTensor), 1, true));
const magnitudeProduct = tf.matMul(magnitude, magnitude.transpose());

const result = similarity.arraySync() as number[][];
const similarity = dotProduct.div(magnitudeProduct);

// Dispose tensors to free memory
matrixTensor.dispose();
dotProduct.dispose();
magnitude.dispose();
magnitudeProduct.dispose();
similarity.dispose();
const result = similarity.arraySync() as number[][];

return result;
// Dispose tensors to free memory
matrixTensor.dispose();
dotProduct.dispose();
magnitude.dispose();
magnitudeProduct.dispose();
similarity.dispose();

return result;
}

export function normalizedCosineSimilarityMatrix(matrix: number[][]) {
// First calculate the similarity matrix
const similarityMatrix = cosineSimilarityMatrix(matrix);

if (similarityMatrix.length === 0 || similarityMatrix[0].length === 0) {
return [[]];
}

// Find the minimum and maximum values in the matrix
let min = Number.POSITIVE_INFINITY;
let max = Number.NEGATIVE_INFINITY;

for (let i = 0; i < similarityMatrix.length; i++) {
for (let j = 0; j < similarityMatrix[i].length; j++) {
min = Math.min(min, similarityMatrix[i][j]);
max = Math.max(max, similarityMatrix[i][j]);
}
}

// Handle edge case where matrix is constant (max == min)
if (max === min) {
// Return a matrix of all zeros
return similarityMatrix.map(row => row.map(() => 0));
}

// Normalize the matrix to [-1, 1] range
const normalizedMatrix = similarityMatrix.map(row =>
row.map(value => 2 * (value - min) / (max - min) - 1)
);

return normalizedMatrix;
}

export function computeAllSums(similarityMatrix: number[][], votingMatrix: number[][]) {
if (similarityMatrix.length === 0 || similarityMatrix[0].length === 0
|| votingMatrix.length === 0 || votingMatrix[0].length === 0) {
Expand Down Expand Up @@ -93,4 +126,4 @@ export function computeAllSums(similarityMatrix: number[][], votingMatrix: numbe
sum_neg_neg.dispose();

return result;
}
}