本文目录导读:

在PHP项目中实现多维缩放(Multidimensional Scaling, MDS)通常需要借助数值计算库或自己实现算法,以下是几种常见方法:
使用PHP科学计算库
使用 math-php 库
use MathPHP\LinearAlgebra\Matrix;
use MathPHP\Statistics\Multivariate\MDS;
// 安装: composer require markrogoyski/math-php
// 准备距离矩阵 (欧几里得距离示例)
$distances = [
[0, 2, 3, 4],
[2, 0, 5, 6],
[3, 5, 0, 7],
[4, 6, 7, 0]
];
// 创建距离矩阵
$distanceMatrix = new Matrix($distances);
// 执行经典MDS
$mds = new MDS($distanceMatrix);
$coordinates = $mds->classical(); // 返回降维后的坐标
// 输出结果 (通常2D或3D坐标系)
foreach ($coordinates as $point) {
echo "X: " . $point[0] . ", Y: " . $point[1] . "\n";
}
手动实现经典MDS算法
class ClassicMDS {
/**
* 执行经典多维缩放
* @param array $distanceMatrix 距离矩阵 (对称)
* @param int $dimensions 目标维度
* @return array 降维后的坐标
*/
public static function compute(array $distanceMatrix, int $dimensions = 2): array {
$n = count($distanceMatrix);
// 1. 距离平方矩阵
$squared = [];
for ($i = 0; $i < $n; $i++) {
for ($j = 0; $j < $n; $j++) {
$squared[$i][$j] = $distanceMatrix[$i][$j] ** 2;
}
}
// 2. 双中心化
$rowMeans = array_map(function($row) {
return array_sum($row) / count($row);
}, $squared);
$colMeans = [];
for ($j = 0; $j < $n; $j++) {
$colMeans[$j] = array_sum(array_column($squared, $j)) / $n;
}
$totalMean = array_sum($rowMeans) / $n;
// 计算内积矩阵 B
$B = [];
for ($i = 0; $i < $n; $i++) {
for ($j = 0; $j < $n; $j++) {
$B[$i][$j] = -0.5 * ($squared[$i][$j] - $rowMeans[$i] - $colMeans[$j] + $totalMean);
}
}
// 3. 特征值分解 (使用幂迭代法近似)
$eigenvalues = [];
$eigenvectors = [];
self::powerIteration($B, $dimensions, $eigenvalues, $eigenvectors);
// 4. 计算坐标
$coordinates = [];
for ($i = 0; $i < $n; $i++) {
$point = [];
for ($k = 0; $k < $dimensions; $k++) {
$point[] = sqrt(max(0, $eigenvalues[$k])) * $eigenvectors[$k][$i];
}
$coordinates[] = $point;
}
return $coordinates;
}
private static function powerIteration(array $matrix, int $k, array &$eigenvalues, array &$eigenvectors): void {
$n = count($matrix);
for ($iter = 0; $iter < $k; $iter++) {
$vector = array_fill(0, $n, 1.0);
// 幂迭代
for ($power = 0; $power < 100; $power++) {
$newVector = array_fill(0, $n, 0);
for ($i = 0; $i < $n; $i++) {
for ($j = 0; $j < $n; $j++) {
$newVector[$i] += $matrix[$i][$j] * $vector[$j];
}
}
// 归一化
$norm = sqrt(array_sum(array_map(function($x) { return $x ** 2; }, $newVector)));
$vector = array_map(function($x) use ($norm) {
return $norm > 0 ? $x / $norm : 0;
}, $newVector);
}
$eigenvectors[$iter] = $vector;
// 计算特征值
$eigenvalues[$iter] = 0;
for ($i = 0; $i < $n; $i++) {
$sum = 0;
for ($j = 0; $j < $n; $j++) {
$sum += $matrix[$i][$j] * $vector[$j];
}
$eigenvalues[$iter] += $sum * $vector[$i];
}
// 去相关 (从矩阵中移除该特征向量)
for ($i = 0; $i < $n; $i++) {
for ($j = 0; $j < $n; $j++) {
$matrix[$i][$j] -= $eigenvalues[$iter] * $vector[$i] * $vector[$j];
}
}
}
}
}
// 使用示例
$distances = [
[0, 2.5, 3.7],
[2.5, 0, 4.2],
[3.7, 4.2, 0]
];
$coordinates = ClassicMDS::compute($distances, 2);
print_r($coordinates);
通过外部工具集成
使用Python脚本 (推荐)
// Python MDS脚本
exec("python3 mds_script.py " . escapeshellarg(json_encode($distanceMatrix)), $output);
$result = json_decode($output[0], true);
// mds_script.py 内容:
// import sys, json
// from sklearn.manifold import MDS
// distances = np.array(json.loads(sys.argv[1]))
// mds = MDS(n_components=2, dissimilarity="precomputed")
// coords = mds.fit_transform(distances)
// print(json.dumps(coords.tolist()))
使用R语言
// 同样可以通过system()调用R脚本 $command = "Rscript mds_script.R " . escapeshellarg(json_encode($distanceMatrix)); exec($command, $output);
性能优化建议
// 1. 对大矩阵使用缓存
$cacheKey = md5(serialize($distanceMatrix));
if ($cached = apcu_fetch($cacheKey)) {
return $cached;
}
// 2. 使用SVD分解替代特征值分解(更稳定)
// 3. 对大型数据集使用批处理
function batchMDS(array $data, int $batchSize = 1000): array {
$result = [];
foreach (array_chunk($data, $batchSize) as $batch) {
$result = array_merge($result, processBatch($batch));
}
return $result;
}
// 4. 使用内存高效的数据结构
$matrix = new SplFixedArray($n);
for ($i = 0; $i < $n; $i++) {
$matrix[$i] = new SplFixedArray($n);
}
完整示例:客户相似度分析
class CustomerMDS {
private $customers = [];
private $distanceMatrix = [];
private $coordinates = [];
public function addCustomers(array $customers): void {
$this->customers = $customers;
}
public function computeSimilarity(array $metrics = ['purchase_amount', 'visit_frequency', 'category_preference']): void {
$n = count($this->customers);
// 标准化数据
$normalized = $this->normalize($metrics);
// 计算欧几里得距离
for ($i = 0; $i < $n; $i++) {
for ($j = $i; $j < $n; $j++) {
$distance = 0;
foreach ($metrics as $metric) {
$diff = $normalized[$i][$metric] - $normalized[$j][$metric];
$distance += $diff * $diff;
}
$this->distanceMatrix[$i][$j] = sqrt($distance);
$this->distanceMatrix[$j][$i] = $this->distanceMatrix[$i][$j];
}
}
// 执行MDS
$this->coordinates = ClassicMDS::compute($this->distanceMatrix, 2);
}
public function getCustomerPosition(int $customerId): array {
$idx = array_search($customerId, array_column($this->customers, 'id'));
return $this->coordinates[$idx] ?? [];
}
public function findNearestCustomers(int $customerId, int $k = 5): array {
$pos = $this->getCustomerPosition($customerId);
$distances = [];
foreach ($this->coordinates as $idx => $coord) {
if ($this->customers[$idx]['id'] !== $customerId) {
$dist = sqrt(($pos[0] - $coord[0])**2 + ($pos[1] - $coord[1])**2);
$distances[$idx] = $dist;
}
}
asort($distances);
return array_slice(array_keys($distances), 0, $k);
}
private function normalize(array $metrics): array {
$min = [];
$max = [];
foreach ($metrics as $metric) {
$values = array_column($this->customers, $metric);
$min[$metric] = min($values);
$max[$metric] = max($values);
}
$normalized = $this->customers;
foreach ($normalized as &$customer) {
foreach ($metrics as $metric) {
$range = $max[$metric] - $min[$metric];
$customer[$metric] = $range > 0 ? ($customer[$metric] - $min[$metric]) / $range : 0;
}
}
return $normalized;
}
}
注意事项
- 数据预处理:确保距离矩阵满足三角不等式
- 维度选择:使用肘部法则确定最佳维度
- 数值稳定性:对负特征值进行处理
- 大规模数据:考虑使用随机MDS或Landmark MDS
对于生产环境,建议使用Python或R等成熟的科学计算工具配合PHP调用,以获得更好的性能和精度。