PHP项目相似检索如何基于向量计算相似度

wen PHP项目 24

本文目录导读:

PHP项目相似检索如何基于向量计算相似度

  1. 使用 PHP 扩展 + 外部向量数据库
  2. 纯 PHP 实现(小规模数据)
  3. 优化的大规模方案
  4. 完整的项目实现示例
  5. 性能优化建议
  6. 选择建议

在PHP项目中实现基于向量的相似度检索,主要有以下几种方法:

使用 PHP 扩展 + 外部向量数据库

Milvus + PHP SDK

// 安装:composer require milvus-io/milvus-sdk-php
use Milvus\Client;
use Milvus\Collection;
use Milvus\Index\IndexType;
// 连接 Milvus
$client = new Client('localhost', 19530);
// 插入向量数据
$collection = $client->createCollection([
    'collection_name' => 'documents',
    'dimension' => 128, // 向量维度
    'metric_type' => MetricType::IP, // 内积相似度
]);
// 搜索相似向量
$results = $collection->search([
    'vector' => [0.1, 0.2, ..., 0.8], // 查询向量
    'top_k' => 10,
    'metric_type' => MetricType::COSINE, // 余弦相似度
]);

Pinecone(云服务)

// 安装:composer require pinecone-io/pinecone-sdk
use Pinecone\Client;
$client = new Client([
    'apiKey' => 'your-api-key',
    'environment' => 'us-west1-gcp'
]);
// 插入向量
$upsertResponse = $client->index('documents')->upsert([
    'vectors' => [
        [
            'id' => 'doc1',
            'values' => [0.1, 0.2, 0.3, 0.4],
            'metadata' => ['title' => '文档1']
        ]
    ]
]);
// 查询相似向量
$queryResponse = $client->index('documents')->query([
    'vector' => [0.1, 0.2, 0.3, 0.4],
    'topK' => 5,
    'includeMetadata' => true
]);

纯 PHP 实现(小规模数据)

余弦相似度计算

class VectorSimilarity {
    /**
     * 计算余弦相似度
     */
    public static function cosineSimilarity(array $vectorA, array $vectorB): float {
        $dotProduct = 0;
        $normA = 0;
        $normB = 0;
        for ($i = 0; $i < count($vectorA); $i++) {
            $dotProduct += $vectorA[$i] * $vectorB[$i];
            $normA += $vectorA[$i] * $vectorA[$i];
            $normB += $vectorB[$i] * $vectorB[$i];
        }
        if ($normA == 0 || $normB == 0) {
            return 0;
        }
        return $dotProduct / (sqrt($normA) * sqrt($normB));
    }
    /**
     * 计算欧几里得距离
     */
    public static function euclideanDistance(array $vectorA, array $vectorB): float {
        $sum = 0;
        for ($i = 0; $i < count($vectorA); $i++) {
            $diff = $vectorA[$i] - $vectorB[$i];
            $sum += $diff * $diff;
        }
        return sqrt($sum);
    }
    /**
     * 计算点积相似度
     */
    public static function dotProduct(array $vectorA, array $vectorB): float {
        $dotProduct = 0;
        for ($i = 0; $i < count($vectorA); $i++) {
            $dotProduct += $vectorA[$i] * $vectorB[$i];
        }
        return $dotProduct;
    }
}
// 使用示例
$vector1 = [0.1, 0.2, 0.3, 0.4];
$vector2 = [0.15, 0.25, 0.35, 0.45];
$similarity = VectorSimilarity::cosineSimilarity($vector1, $vector2);
echo "余弦相似度: " . $similarity;

向量索引实现(小规模)

class VectorIndex {
    private array $vectors = [];
    private array $metadata = [];
    /**
     * 添加向量到索引
     */
    public function add(string $id, array $vector, array $meta = []): void {
        $this->vectors[$id] = $vector;
        $this->metadata[$id] = $meta;
    }
    /**
     * 搜索最相似的向量
     */
    public function search(array $queryVector, int $topK = 10): array {
        $scores = [];
        foreach ($this->vectors as $id => $vector) {
            $score = VectorSimilarity::cosineSimilarity($queryVector, $vector);
            $scores[$id] = $score;
        }
        // 排序获取 topK
        arsort($scores);
        $results = [];
        $count = 0;
        foreach ($scores as $id => $score) {
            if ($count >= $topK) break;
            $results[] = [
                'id' => $id,
                'score' => $score,
                'metadata' => $this->metadata[$id] ?? []
            ];
            $count++;
        }
        return $results;
    }
    /**
     * 批量添加向量
     */
    public function batchAdd(array $vectors): void {
        foreach ($vectors as $item) {
            $this->add($item['id'], $item['vector'], $item['metadata'] ?? []);
        }
    }
}

优化的大规模方案

使用 SQLite + FTS5 + 向量扩展

// 需要安装 sqlite-vector 扩展
class SQLiteVectorSearch {
    private PDO $pdo;
    public function __construct(string $dbPath) {
        $this->pdo = new PDO("sqlite:$dbPath");
        $this->initDatabase();
    }
    private function initDatabase(): void {
        $this->pdo->exec("
            CREATE VIRTUAL TABLE IF NOT EXISTS vectors USING vec0(
                id INTEGER PRIMARY KEY,
                embedding float[128]
            )
        ");
    }
    public function insert(int $id, array $vector): void {
        $stmt = $this->pdo->prepare(
            "INSERT INTO vectors (id, embedding) VALUES (?, ?)"
        );
        $stmt->execute([$id, json_encode($vector)]);
    }
    public function search(array $queryVector, int $limit = 10): array {
        $stmt = $this->pdo->prepare("
            SELECT 
                id,
                distance
            FROM vectors
            WHERE embedding MATCH ?
            ORDER BY distance
            LIMIT ?
        ");
        $stmt->execute([json_encode($queryVector), $limit]);
        return $stmt->fetchAll(PDO::FETCH_ASSOC);
    }
}

完整的项目实现示例

class DocumentSearchEngine {
    private VectorIndex $index;
    private array $documents;
    public function __construct() {
        $this->index = new VectorIndex();
        $this->documents = [];
    }
    /**
     * 添加文档
     */
    public function addDocument(string $id, string $content): void {
        $vector = $this->textToVector($content);
        $this->documents[$id] = $content;
        $this->index->add($id, $vector, [
            'content' => $content,
            'length' => strlen($content)
        ]);
    }
    /**
     * 将文本转换为向量(简化示例)
     * 实际应用中应使用如 OpenAI Embedding API 或本地模型
     */
    private function textToVector(string $text): array {
        // 这是一个简化的示例,实际应该使用词嵌入模型
        $words = str_word_count($text, 1);
        $vector = array_fill(0, 128, 0);
        foreach ($words as $word) {
            $hash = crc32($word);
            $index = abs($hash) % 128;
            $vector[$index] += 1;
        }
        // 归一化
        $norm = sqrt(array_sum(array_map(function($v) { 
            return $v * $v; 
        }, $vector)));
        if ($norm > 0) {
            $vector = array_map(function($v) use ($norm) {
                return $v / $norm;
            }, $vector);
        }
        return $vector;
    }
    /**
     * 搜索相似文档
     */
    public function search(string $query, int $limit = 5): array {
        $queryVector = $this->textToVector($query);
        $results = $this->index->search($queryVector, $limit);
        // 格式化结果
        $formatted = [];
        foreach ($results as $result) {
            $formatted[] = [
                'id' => $result['id'],
                'content' => $this->documents[$result['id']],
                'score' => $result['score'],
                'relevance' => round($result['score'] * 100, 2) . '%'
            ];
        }
        return $formatted;
    }
}
// 使用示例
$engine = new DocumentSearchEngine();
// 添加文档
$engine->addDocument('1', 'PHP是一种流行的服务器端脚本语言');
$engine->addDocument('2', 'JavaScript用于前端网页开发');
$engine->addDocument('3', 'Python在数据科学领域广泛应用');
// 搜索
$results = $engine->search('编程语言');
foreach ($results as $result) {
    echo "文档: {$result['content']}\n";
    echo "相似度: {$result['relevance']}\n\n";
}

性能优化建议

class OptimizedVectorSearch {
    /**
     * 使用近似最近邻算法(ANN)
     */
    public function approximateSearch(array $query, int $k = 10): array {
        // 使用空间分割或聚类算法
        // KD-Tree, R-Tree, Product Quantization
    }
    /**
     * 分片索引
     */
    public function shardSearch(array $query, int $shards = 4): array {
        $results = [];
        // 并行搜索多个分片
        $this->parallelSearch($query, $shards, $results);
        return $this->mergeResults($results);
    }
    /**
     * 向量量化压缩
     */
    private function quantizeVector(array $vector, int $bits = 8): array {
        // 将浮点数向量量化为低精度整数
        $quantized = [];
        foreach ($vector as $value) {
            $quantized[] = (int)($value * (pow(2, $bits) - 1));
        }
        return $quantized;
    }
}

选择建议

  1. 数据量 < 1万:使用纯PHP实现
  2. 数据量 1万-100万:使用SQLite向量扩展
  3. 数据量 > 100万:使用Milvus、Pinecone等专业向量数据库
  4. 需要实时更新:选择支持动态更新的方案
  5. 高并发场景:使用异步架构和缓存优化

这些方法可以根据你的具体需求进行调整,需要注意,实际的文本向量化通常需要使用预训练模型(如BERT、Word2Vec等),这里为了示例使用了简化的哈希方法。

抱歉,评论功能暂时关闭!