本文目录导读:

在PHP项目中实现相似度分析,常见的有以下几种方法,具体选择取决于你的数据类型和场景:
文本相似度分析
1 余弦相似度(最常用)
适用于长文本、文档比较。
function cosineSimilarity($text1, $text2) {
// 分词
$words1 = str_word_count(strtolower($text1), 1);
$words2 = str_word_count(strtolower($text2), 1);
// 创建词频向量
$vector = array_unique(array_merge($words1, $words2));
$vec1 = array_fill_keys($vector, 0);
$vec2 = array_fill_keys($vector, 0);
foreach ($words1 as $word) {
$vec1[$word]++;
}
foreach ($words2 as $word) {
$vec2[$word]++;
}
// 计算点积和模长
$dotProduct = 0;
$magnitude1 = 0;
$magnitude2 = 0;
foreach ($vector as $word) {
$dotProduct += $vec1[$word] * $vec2[$word];
$magnitude1 += pow($vec1[$word], 2);
$magnitude2 += pow($vec2[$word], 2);
}
$magnitude1 = sqrt($magnitude1);
$magnitude2 = sqrt($magnitude2);
if ($magnitude1 * $magnitude2 == 0) return 0;
return $dotProduct / ($magnitude1 * $magnitude2);
}
2 Jaccard 相似度
适用于短文本、关键词比较。
function jaccardSimilarity($text1, $text2) {
$words1 = array_unique(str_word_count(strtolower($text1), 1));
$words2 = array_unique(str_word_count(strtolower($text2), 1));
$intersection = array_intersect($words1, $words2);
$union = array_unique(array_merge($words1, $words2));
if (empty($union)) return 0;
return count($intersection) / count($union);
}
3 Levenshtein 距离(编辑距离)
适用于字符串精确匹配。
function levenshteinSimilarity($str1, $str2) {
$maxLen = max(strlen($str1), strlen($str2));
if ($maxLen == 0) return 1;
$distance = levenshtein($str1, $str2);
return 1 - ($distance / $maxLen);
}
数组/集合相似度
function arraySimilarity(array $arr1, array $arr2) {
$intersection = array_intersect($arr1, $arr2);
$union = array_unique(array_merge($arr1, $arr2));
return count($intersection) / count($union);
}
使用扩展库(推荐)
1 使用 php-text-similarity 包
通过 Composer 安装:
composer require jacobemerick/text-similarity
示例代码:
use Jacobemerick\TextSimilarity\CosineSimilarity;
use Jacobemerick\TextSimilarity\JaccardSimilarity;
$cosine = new CosineSimilarity();
$similarity = $cosine->getSimilarity('Hello world', 'Hello there');
$jaccard = new JaccardSimilarity();
$similarity = $jaccard->getSimilarity('Hello world', 'Hello there');
2 使用 php-similarity 包
composer require dipcode/php-similarity
use Dipcode\PhpSimilarity\CosineSimilarity;
$similarity = new CosineSimilarity();
$score = $similarity->calculate('phrase 1', 'phrase 2');
中文文本相似度
对于中文,需要先进行分词:
// 使用 jieba-php 分词
composer require fukuball/jieba-php
require_once __DIR__ . '/vendor/autoload.php';
use Fukuball\Jieba\Jieba;
use Fukuball\Jieba\Finalseg;
Jieba::init();
Finalseg::init();
function chineseCosineSimilarity($text1, $text2) {
// 分词
$words1 = Jieba::cut($text1);
$words2 = Jieba::cut($text2);
// 使用 TF-IDF 向量化
$tfidf1 = getTfIdf($words1);
$tfidf2 = getTfIdf($words2);
// 计算余弦相似度
return cosineSimilarityForVectors($tfidf1, $tfidf2);
}
完整示例:文本相似度分析服务
class TextSimilarityService {
/**
* 计算文本相似度
*/
public function calculateSimilarity($text1, $text2, $method = 'cosine') {
switch ($method) {
case 'cosine':
return $this->cosineSimilarity($text1, $text2);
case 'jaccard':
return $this->jaccardSimilarity($text1, $text2);
case 'levenshtein':
return $this->levenshteinSimilarity($text1, $text2);
default:
throw new InvalidArgumentException("Unknown method: $method");
}
}
/**
* 批量比较多个文本
*/
public function batchCompare($baseText, array $texts, $method = 'cosine') {
$results = [];
foreach ($texts as $id => $text) {
$results[$id] = [
'text' => $text,
'similarity' => $this->calculateSimilarity($baseText, $text, $method)
];
}
// 按相似度排序
usort($results, function($a, $b) {
return $b['similarity'] <=> $a['similarity'];
});
return $results;
}
/**
* 查找相似文本(超过阈值)
*/
public function findSimilar($baseText, array $texts, $threshold = 0.5, $method = 'cosine') {
$results = $this->batchCompare($baseText, $texts, $method);
return array_filter($results, function($item) use ($threshold) {
return $item['similarity'] >= $threshold;
});
}
private function cosineSimilarity($text1, $text2) {
// 前面定义的余弦相似度函数
}
private function jaccardSimilarity($text1, $text2) {
// 前面定义的 Jaccard 相似度函数
}
private function levenshteinSimilarity($str1, $str2) {
// 前面定义的 Levenshtein 相似度函数
}
}
// 使用示例
$service = new TextSimilarityService();
$similarity = $service->calculateSimilarity('Hello world', 'Hello PHP', 'cosine');
性能优化建议
- 缓存分词结果:如果频繁处理相同文本
- 使用向量数据库:对于大规模数据,使用 Elasticsearch 或 Milvus
- 异步处理:大批量数据时使用队列
- 预处理文本:去除标点、停用词、统一大小写
选择合适的算法
| 场景 | 推荐算法 |
|---|---|
| 长文档比较 | 余弦相似度 + TF-IDF |
| 短文本/关键词 | Jaccard 相似度 |
| 字符串精确匹配 | Levenshtein 距离 |
| 英文文本 | 余弦相似度 |
| 中文文本 | 分词 + 余弦相似度 |
| 代码比较 | Levenshtein 距离 |
根据你的具体需求选择最适合的方法,对于简单的应用场景,内置函数如 similar_text() 和 levenshtein() 就足够了。