如何反转JavaScript中多个向量的平均?

How to reverse the averaging of multiple vectors in JavaScript?

在下面的代码中,我将各种口味的冰淇淋(巧克力、草莓、香草和那不勒斯)混合在一起,以产生一种前所未见的新口味冰淇淋。*

口味由一个数组表示,其中第一个元素只是一个字符串,即口味的名称。

第二个元素是从0100的数字,代表香草成分,第三个元素是巧克力成分,第四个元素是草莓成分。

混合是通过将所有输入风味(数组)平均在一起来执行的。

混合后,我尝试确定新混合物与哪种口味最相似。这是通过将神秘冰淇淋和已知口味的绝对差相加而得出的。总和越小,差异越小,相似度越大。

在这个具体的例子中,混合物是 6 份草莓冰淇淋和 1 份其他口味的每一种。不出所料,算出来草莓最相似,其次是那不勒斯,因为它本身就是混合物

这是对混合物进行逆向工程的好方法,但我想更进一步。我想确定混合物中每种口味的精确比例。

在此示例中,它将如上所述:6 草莓,1 香草,1 巧克力,1 那不勒斯。

当然,可能有很多(无限?)方法可以得出给定的混合物。但我正在寻找最简约的可能性。

例如,1 份 neopolitan 加 1 份草莓与 4 份草莓加 3 份其他所有口味相同。但前者更为节俭。

我将如何预测混合物的产生方式?

我不知道这个的技术术语是什么。

const mixture = mixIcecreams([
  ['vanilla', 100, 0, 0],
  ['chocolate', 0, 100, 0],
  ['neapolitan', 33, 33, 33],
  ['strawberry', 0, 0, 100],
  ['strawberry', 0, 0, 100],
  ['strawberry', 0, 0, 100],
  ['strawberry', 0, 0, 100],
  ['strawberry', 0, 0, 100],
  ['strawberry', 0, 0, 100],
]);

console.log(mixture);

const distances = calculateDistances(mixture, [
  ['vanilla', 100, 0, 0],
  ['chocolate', 0, 100, 0],
  ['strawberry', 0, 0, 100],
  ['neapolitan', 33, 33, 33],
]);

console.log('Distances:');

console.log(distances);

console.log(
  `The icecream named "${mixture[0]}" is most similar to "${distances[0][0]}" icecream.`
);


// Calculate the "distance" between a "target" vector and "sources" vectors.
// Smaller distance means more similarity.
function calculateDistances(target, sources) {
  return (
    sources
      .map((source) => [
        // First element is the label.
        source[0],
        target.reduce(
          (distance, value, i) =>
            // Avoid doing math with the first element (the label).
            i === 0 ? distance : distance + Math.abs(source[i] - value),
          0
        ),
      ])
      // Sort by shortest distance (most similar).
      .sort((a, b) => a[1] - b[1])
  );
}

function mixIcecreams(icecreams) {
  return icecreams.reduce(
    (mixture, icecream, i) => {
      icecream.forEach((value, j) => j !== 0 && (mixture[j] += value));
      if (i === icecreams.length - 1) {
        return mixture.map((value, j) =>
          // Ignore the first element, it's just a label.
          j === 0 ? value : value / icecreams.length
        );
      }
      return mixture;
    },
    Array.from({ length: icecreams[0].length }, (_, i) =>
      i === 0 ? 'mixture' : 0
    )
  );
}

*正在申请专利。

如果我对你的问题的理解正确,在数学术语中你似乎需要在最小二乘意义上解欠定方程组。

我提出了一个可以改进的快速解决方案。

如果有趣的话,我可以进一步解释。

编辑:我添加了一个简单的整数近似值,以找到最接近百分比的整数解。

const mixture = mixIcecreams([
   ['vanilla', 100, 0, 0],
   ['chocolate', 0, 100, 0],
   ['neapolitan', 33, 33, 33],
   ['strawberry', 0, 0, 100],
   ['strawberry', 0, 0, 100],
   ['strawberry', 0, 0, 100],
   ['strawberry', 0, 0, 100],
   ['strawberry', 0, 0, 100],
   ['strawberry', 0, 0, 100],
]);

console.log(mixture);

const distances = calculateDistances(mixture, [
   ['vanilla', 100, 0, 0],
   ['chocolate', 0, 100, 0],
   ['strawberry', 0, 0, 100],
   ['neapolitan', 33, 33, 33],
]);

console.log('Distances:');

console.log(distances);

console.log(
   `The icecream named "${mixture[0]}" is most similar to "${distances[0][0]}" icecream.`
);


const probableMixture = mostProbableMixture(mixture, [
      ['vanilla', 100, 0, 0],
      ['chocolate', 0, 100, 0],
      ['strawberry', 0, 0, 100],
      ['neapolitan', 33, 33, 33],
   ]
);

console.log('most likely mixture, percent', probableMixture)
const im = integerMix(probableMixture, 100);
// the second argument, nMax, is the maximum number allowed from one basic flavor
// the larger nMax, the closer you may get to the exact mixture

console.log('integer mixture', im)
const rm = repeatIntegerMix(im, [
   ['vanilla', 100, 0, 0],
   ['chocolate', 0, 100, 0],
   ['strawberry', 0, 0, 100],
   ['neapolitan', 33, 33, 33],
]);
console.log('verify mixture', mixIcecreams(rm))


// Calculate the "distance" between a "target" vector and "sources" vectors.
// Smaller distance means more similarity.
function calculateDistances(target, sources) {
   return (
      sources
         .map((source) => [
            // First element is the label.
            source[0],
            target.reduce(
               (distance, value, i) =>
                  // Avoid doing math with the first element (the label).
                  i === 0 ? distance : distance + Math.abs(source[i] - value),
               0
            ),
         ])
         // Sort by shortest distance (most similar).
         .sort((a, b) => a[1] - b[1])
   );
}

function mixIcecreams(icecreams) {
   return icecreams.reduce(
      (mixture, icecream, i) => {
         icecream.forEach((value, j) => j !== 0 && (mixture[j] += value));
         if (i === icecreams.length - 1) {
            return mixture.map((value, j) =>
               // Ignore the first element, it's just a label.
               j === 0 ? value : value / icecreams.length
            );
         }
         return mixture;
      },
      Array.from({ length: icecreams[0].length }, (_, i) =>
         i === 0 ? 'mixture' : 0
      )
   );
}



function mostProbableMixture(mixture, baseFlavors){
   const nVars = baseFlavors.length,
      nEq = mixture.length - 1,
      At = baseFlavors.map(flavor=>flavor.slice(1)),
      b = mixture.slice(1),
      AAt = Array(nEq).fill(0).map(z=>Array(nEq));
   
   //compute A*At
   for(let i = 0; i < nEq; i++){
      for(let j = 0; j < nEq; j++){
         AAt[i][j] = 0;
         for(let k = 0; k < nVars; k++){
            AAt[i][j] += At[k][i]*At[k][j];
         }
      }
   }
   // normalize rows    
   for(let i = 0; i < nEq; i++){
      let maxRow = Math.abs(b[i]);
      for(let j = 0; j < nEq; j++){
         maxRow = Math.max(maxRow, Math.abs(AAt[i][j]));
      }
      for(let j = 0; j < nEq; j++){
         AAt[i][j] = AAt[i][j] /maxRow;
      }
      b[i] = b[i]/maxRow;
   }
   
   // Solve (for t) A * At * t = b
   // Gaussian elimination; diagonal dominance, no pivoting
   for(let j = 0; j < nEq-1; j++){
      for(let i = j+1; i < nEq; i++){
         let f = AAt[i][j] / AAt[j][j];
         for(let k = j+1; k < nEq; k++){
            AAt[i][k] -= f * AAt[j][k];
         }
         b[i] -= f*b[j];
      }
   }
   const t = Array(nEq).fill(0);
   t[nEq-1] = b[nEq-1]/AAt[nEq-1][nEq-1];
   for(let i = nEq-2; i >= 0; i--){
      let s = b[i];
      for(let j = i + 1; j<nEq; j++){
         s -= AAt[i][j]*t[j];
      }
      t[i] = s/AAt[i][i];
   }
   
   // the solution is y = At * t
   const y = Array(nVars).fill(0);
   let sy = 0;
   for(let i = 0; i < nVars; i++){
      for(let j = 0; j < nEq; j++){
         y[i] += At[i][j] * t[j];
      }
      sy += y[i];
   }
   
   for(let i = 0; i < nVars; i++){
      y[i] = y[i]/sy*100;
   }
   
   return y.map((yi, i)=>[baseFlavors[i][0], yi]);
   
}

function integerMix(floatMix, nMax){
   const y = floatMix.map(a=>a[1]),
      maxY = y.reduce((m,v)=>Math.max(m,v));
   let minAlpha = 0, minD = 1e6;
   for(let alpha = 1/maxY; alpha <= nMax/maxY; alpha+=(nMax-1)/10000/maxY){
      //needs refining!!
      const sdif = y.map(v=>Math.pow(Math.round(v*alpha)-v*alpha, 2));
      const d2 = sdif.reduce((s,x)=>s+x);
      if(d2 < minD){
         minD = d2;
         minAlpha = alpha;
      }
   }
   return floatMix.map(([name, f]) => [name, Math.round(minAlpha*f)]);
}


function repeatIntegerMix(integerMix, baseFlavors){
   return integerMix.flatMap(([name, n], i)=> Array.from({length: n}, e=>baseFlavors[i].slice(0)));
}