本文目录导读:

在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 |
性能优化建议
- 缓存计算结果 - 对常见的查询结果进行缓存
- 限制搜索范围 - 使用分页和限制返回结果数量
- 异步处理 - 对于大规模数据处理,使用消息队列
- 索引优化 - 定期重建索引,优化数据存储结构
选择哪种方案主要取决于你的具体需求:数据量大小、实时性要求、硬件资源和开发团队的技术栈,对于大多数PHP项目,Elasticsearch是最推荐的方案。
版权声明:足球直播
足球直播
足球直播
足球直播
足球直播
中超直播
中超直播
中超直播
中超直播
中超直播
英超直播
英超直播
英超直播
英超直播
英超直播
德甲直播
德甲直播
德甲直播
德甲直播
德甲直播
西甲直播
西甲直播
西甲直播
西甲直播
西甲直播
意甲直播
意甲直播
意甲直播
意甲直播
意甲直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
篮球直播
篮球直播
篮球直播
篮球直播
篮球直播
足球直播
足球直播
足球直播
足球直播
足球直播
中超直播
中超直播
中超直播
中超直播
中超直播
英超直播
英超直播
英超直播
英超直播
英超直播
德甲直播
德甲直播
德甲直播
德甲直播
德甲直播
西甲直播
西甲直播
西甲直播
西甲直播
西甲直播
意甲直播
意甲直播
意甲直播
意甲直播
意甲直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
篮球直播
篮球直播
篮球直播
篮球直播
篮球直播
足球直播
足球直播
足球直播
足球直播
足球直播
中超直播
中超直播
中超直播
中超直播
中超直播
英超直播
英超直播
英超直播
英超直播
英超直播
德甲直播
德甲直播
德甲直播
德甲直播
德甲直播
西甲直播
西甲直播
西甲直播
西甲直播
西甲直播
意甲直播
意甲直播
意甲直播
意甲直播
意甲直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
篮球直播
篮球直播
篮球直播
篮球直播
篮球直播