怎样在PHP项目中实现相似性搜索?

wen java案例 1

本文目录导读:

怎样在PHP项目中实现相似性搜索?

  1. 基于文本相似度算法
  2. 使用全文搜索引擎
  3. 向量相似度搜索(高级方案)
  4. 使用专用相似度搜索库
  5. 基于 MySQL 的近似搜索
  6. 完整示例:基于 TF-IDF 的相似度系统
  7. 选择建议
  8. 性能优化建议

在PHP项目中实现相似性搜索,通常有几种不同的方法,具体选择取决于你的数据规模、精度要求和性能需求,以下是几种常见方案的详细介绍:

基于文本相似度算法

1 使用相似度函数

<?php
// Levenshtein 距离(编辑距离)
$word1 = "apple";
$word2 = "appl";
$distance = levenshtein($word1, $word2);
// 距离越小越相似,0表示完全相同
// similar_text(基于字符匹配)
$similarity = similar_text($word1, $word2, $percent);
// $percent 是相似度百分比
// 自定义 Jaccard 相似度
function jaccardSimilarity($str1, $str2) {
    $set1 = array_unique(str_split($str1));
    $set2 = array_unique(str_split($str2));
    $intersection = array_intersect($set1, $set2);
    $union = array_unique(array_merge($set1, $set2));
    return count($intersection) / count($union);
}
?>

使用全文搜索引擎

1 Elasticsearch(推荐方案)

安装客户端:

composer require elasticsearch/elasticsearch

PHP实现:

<?php
require 'vendor/autoload.php';
use Elastic\Elasticsearch\ClientBuilder;
class SimilaritySearch {
    private $client;
    public function __construct() {
        $this->client = ClientBuilder::create()
            ->setHosts(['localhost:9200'])
            ->build();
    }
    // 创建索引并设置自定义相似度
    public function createIndex($indexName) {
        $params = [
            'index' => $indexName,
            'body' => [
                'settings' => [
                    'similarity' => [
                        'custom_similarity' => [
                            'type' => 'BM25',
                            'k1' => 1.2,
                            'b' => 0.75
                        ]
                    ]
                ],
                'mappings' => [
                    'properties' => [
                        'title' => [
                            'type' => 'text',
                            'similarity' => 'custom_similarity'
                        ],
                        'content' => [
                            'type' => 'text'
                        ]
                    ]
                ]
            ]
        ];
        return $this->client->indices()->create($params);
    }
    // 执行相似性搜索
    public function search($index, $query) {
        $params = [
            'index' => $index,
            'body' => [
                'query' => [
                    'more_like_this' => [
                        'fields' => ['title', 'content'],
                        'like' => $query,
                        'min_term_freq' => 1,
                        'max_query_terms' => 12
                    ]
                ],
                'size' => 10
            ]
        ];
        return $this->client->search($params);
    }
}
?>

向量相似度搜索(高级方案)

1 使用词向量(Word Embeddings)

<?php
class VectorSimilarity {
    // 余弦相似度计算
    public function cosineSimilarity($vectorA, $vectorB) {
        $dotProduct = 0;
        $normA = 0;
        $normB = 0;
        foreach ($vectorA as $i => $value) {
            $dotProduct += $value * $vectorB[$i];
            $normA += $value * $value;
            $normB += $vectorB[$i] * $vectorB[$i];
        }
        if ($normA == 0 || $normB == 0) return 0;
        return $dotProduct / (sqrt($normA) * sqrt($normB));
    }
    // 简单词袋模型向量化
    public function vectorize($text, $vocabulary) {
        $words = str_word_count(strtolower($text), 1);
        $vector = array_fill(0, count($vocabulary), 0);
        foreach ($words as $word) {
            if (isset($vocabulary[$word])) {
                $vector[$vocabulary[$word]]++;
            }
        }
        return $vector;
    }
}
// 使用示例
$vectorSimilarity = new VectorSimilarity();
// 构建词汇表
$vocabulary = [
    'apple' => 0,
    'orange' => 1,
    'fruit' => 2,
    'juice' => 3
];
$text1 = "apple fruit juice";
$text2 = "orange juice";
$vector1 = $vectorSimilarity->vectorize($text1, $vocabulary);
$vector2 = $vectorSimilarity->vectorize($text2, $vocabulary);
$similarity = $vectorSimilarity->cosineSimilarity($vector1, $vector2);
echo "相似度: " . $similarity;
?>

使用专用相似度搜索库

1 安装 Meilisearch

composer require meilisearch/meilisearch-php
<?php
require 'vendor/autoload.php';
use Meilisearch\Client;
class MeiliSearchService {
    private $client;
    public function __construct() {
        $this->client = new Client('http://localhost:7700', 'masterKey');
    }
    // 添加文档
    public function addDocuments($index, $documents) {
        return $this->client->index($index)->addDocuments($documents);
    }
    // 相似性搜索(使用模糊匹配)
    public function search($index, $query) {
        return $this->client->index($index)->search($query, [
            'attributesToSearchOn' => ['title', 'description'],
            'showRankingScore' => true,
            'fuzzy' => true
        ]);
    }
}
?>

基于 MySQL 的近似搜索

<?php
class MySQLSimilaritySearch {
    private $pdo;
    public function __construct() {
        $this->pdo = new PDO('mysql:host=localhost;dbname=test', 'user', 'password');
    }
    // 创建全文索引
    public function createFulltextIndex($table, $columns) {
        $columns = implode(', ', $columns);
        $sql = "ALTER TABLE $table ADD FULLTEXT INDEX ft_index ($columns)";
        $this->pdo->exec($sql);
    }
    // 使用全文搜索进行相似度匹配
    public function searchSimilar($table, $query, $limit = 10) {
        $sql = "SELECT *, MATCH(title, content) AGAINST(:query IN NATURAL LANGUAGE MODE) AS relevance 
                FROM $table 
                WHERE MATCH(title, content) AGAINST(:query IN NATURAL LANGUAGE MODE) 
                ORDER BY relevance DESC 
                LIMIT :limit";
        $stmt = $this->pdo->prepare($sql);
        $stmt->execute(['query' => $query, 'limit' => $limit]);
        return $stmt->fetchAll(PDO::FETCH_ASSOC);
    }
}
?>

完整示例:基于 TF-IDF 的相似度系统

<?php
class TfIdfSimilarity {
    private $documents = [];
    private $idfValues = [];
    public function addDocument($id, $text) {
        $words = $this->tokenize($text);
        $this->documents[$id] = $words;
    }
    private function tokenize($text) {
        // 简单的分词处理
        $text = strtolower($text);
        $words = str_word_count($text, 1);
        // 去除停用词
        $stopWords = ['the', 'a', 'an', 'and', 'or', 'but', 'in', 'on', 'at', 'to', 'for'];
        return array_diff($words, $stopWords);
    }
    public function calculateIdf() {
        $totalDocs = count($this->documents);
        // 计算每个词的文档频率
        $docFrequency = [];
        foreach ($this->documents as $words) {
            $uniqueWords = array_unique($words);
            foreach ($uniqueWords as $word) {
                if (!isset($docFrequency[$word])) {
                    $docFrequency[$word] = 0;
                }
                $docFrequency[$word]++;
            }
        }
        // 计算 IDF
        foreach ($docFrequency as $word => $freq) {
            $this->idfValues[$word] = log($totalDocs / ($freq + 1));
        }
    }
    public function calculateTfIdf($words) {
        $tfIdf = [];
        $wordCount = count($words);
        foreach ($words as $word) {
            // 计算 TF
            $tf = array_count_values($words)[$word] / $wordCount;
            // 计算 TF-IDF
            $idf = isset($this->idfValues[$word]) ? $this->idfValues[$word] : 0;
            $tfIdf[$word] = $tf * $idf;
        }
        return $tfIdf;
    }
    public function search($query) {
        $queryWords = $this->tokenize($query);
        $queryVector = $this->calculateTfIdf($queryWords);
        $scores = [];
        foreach ($this->documents as $docId => $docWords) {
            $docVector = $this->calculateTfIdf($docWords);
            // 计算余弦相似度
            $similarity = $this->cosineSimilarity($queryVector, $docVector);
            $scores[$docId] = $similarity;
        }
        // 按相似度排序
        arsort($scores);
        return $scores;
    }
    private function cosineSimilarity($vectorA, $vectorB) {
        $dotProduct = 0;
        $normA = 0;
        $normB = 0;
        $allKeys = array_unique(array_merge(array_keys($vectorA), array_keys($vectorB)));
        foreach ($allKeys as $key) {
            $a = isset($vectorA[$key]) ? $vectorA[$key] : 0;
            $b = isset($vectorB[$key]) ? $vectorB[$key] : 0;
            $dotProduct += $a * $b;
            $normA += $a * $a;
            $normB += $b * $b;
        }
        if ($normA == 0 || $normB == 0) return 0;
        return $dotProduct / (sqrt($normA) * sqrt($normB));
    }
}
// 使用示例
$tfIdf = new TfIdfSimilarity();
$tfIdf->addDocument(1, "PHP is a popular programming language for web development");
$tfIdf->addDocument(2, "JavaScript is used for both frontend and backend development");
$tfIdf->addDocument(3, "Python excels in data science and machine learning");
$tfIdf->calculateIdf();
$results = $tfIdf->search("web development language");
foreach ($results as $docId => $score) {
    echo "Document $docId: $score\n";
}
?>

选择建议

数据规模 精度要求 推荐方案
小(<1000条) 一般 MySQL全文搜索或PHP内置函数
中(<10万条) 较高 Elasticsearch或Meilisearch
大(>10万条) 很高 向量数据库(Milvus/Pinecone)+ Embeddings
实时搜索 Elasticsearch/Meilisearch

性能优化建议

  1. 缓存计算结果 - 对常见的查询结果进行缓存
  2. 限制搜索范围 - 使用分页和限制返回结果数量
  3. 异步处理 - 对于大规模数据处理,使用消息队列
  4. 索引优化 - 定期重建索引,优化数据存储结构

选择哪种方案主要取决于你的具体需求:数据量大小、实时性要求、硬件资源和开发团队的技术栈,对于大多数PHP项目,Elasticsearch是最推荐的方案。

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