本文目录导读:

- 莱文斯坦距离(Levenshtein Distance)
- similar_text 函数
- 余弦相似度(Cosine Similarity)
- Jaccard 相似度
- 使用第三方库(推荐)
- 完整示例:文本比较系统
- 性能优化建议
- 选择合适的算法
- 注意事项
在PHP项目中实现相似度计算,主要可以通过以下几种常见方法,我会从基础到高级逐步介绍:
莱文斯坦距离(Levenshtein Distance)
PHP内置函数,计算两个字符串之间的编辑距离。
<?php
// 基础用法
$text1 = "hello world";
$text2 = "hallo world";
$distance = levenshtein($text1, $text2);
echo "编辑距离: " . $distance; // 输出: 1
// 转换为相似度百分比
function levenshteinSimilarity($str1, $str2) {
$distance = levenshtein($str1, $str2);
$maxLen = max(strlen($str1), strlen($str2));
if ($maxLen == 0) return 1; // 两个空字符串完全相似
return 1 - ($distance / $maxLen);
}
echo "相似度: " . levenshteinSimilarity($text1, $text2) * 100 . "%";
similar_text 函数
PHP内置函数,基于字符匹配。
<?php $text1 = "PHP is great"; $text2 = "PHP is good"; $percent = 0; similar_text($text1, $text2, $percent); echo "相似度: " . round($percent, 2) . "%"; // 输出: 66.67%
余弦相似度(Cosine Similarity)
适用于文本向量化后的比较,特别适合TF-IDF场景。
<?php
function cosineSimilarity($text1, $text2) {
// 分词和获取词频
$words1 = array_count_values(str_word_count(strtolower($text1), 1));
$words2 = array_count_values(str_word_count(strtolower($text2), 1));
// 获取所有唯一词
$allWords = array_unique(array_merge(array_keys($words1), array_keys($words2)));
// 构建向量
$dotProduct = 0;
$magnitude1 = 0;
$magnitude2 = 0;
foreach ($allWords as $word) {
$count1 = $words1[$word] ?? 0;
$count2 = $words2[$word] ?? 0;
$dotProduct += $count1 * $count2;
$magnitude1 += $count1 * $count1;
$magnitude2 += $count2 * $count2;
}
$magnitude1 = sqrt($magnitude1);
$magnitude2 = sqrt($magnitude2);
if ($magnitude1 == 0 || $magnitude2 == 0) return 0;
return $dotProduct / ($magnitude1 * $magnitude2);
}
$text1 = "I love programming in PHP";
$text2 = "I love coding in PHP";
echo "余弦相似度: " . round(cosineSimilarity($text1, $text2) * 100, 2) . "%";
Jaccard 相似度
基于集合的交集与并集。
<?php
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 (count($union) == 0) return 1;
return count($intersection) / count($union);
}
$text1 = "the quick brown fox";
$text2 = "the quick brown dog";
echo "Jaccard相似度: " . round(jaccardSimilarity($text1, $text2) * 100, 2) . "%";
使用第三方库(推荐)
使用 PHP NLP Tools
composer require php-nlp-tools/php-nlp-tools
<?php require_once 'vendor/autoload.php'; use NlpTools\Similarity\CosineSimilarity; use NlpTools\Similarity\JaccardSimilarity; // 余弦相似度 $cosine = new CosineSimilarity(); $vector1 = ['php' => 1, 'programming' => 1, 'language' => 1]; $vector2 = ['php' => 1, 'coding' => 1, 'language' => 1]; echo $cosine->similarity($vector1, $vector2); // 0.666... // Jaccard相似度 $jaccard = new JaccardSimilarity(); $set1 = ['php', 'programming', 'language']; $set2 = ['php', 'coding', 'language']; echo $jaccard->similarity($set1, $set2); // 0.5
使用 Text Similarity PHP
composer require yooper/php-text-analysis
<?php require_once 'vendor/autoload.php'; use PhpTextAnalysis\Similarity\CosineSimilarity; $similarity = new CosineSimilarity(); $text1 = "The quick brown fox jumps over the lazy dog"; $text2 = "The quick brown fox jumps over the sleeping cat"; $score = $similarity->similarity($text1, $text2); echo "相似度分数: " . $score;
完整示例:文本比较系统
<?php
class TextSimilarity {
/**
* 计算文本相似度
*/
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");
}
}
/**
* 文本预处理
*/
private function preprocess($text) {
// 转为小写
$text = strtolower($text);
// 移除标点符号
$text = preg_replace('/[^\w\s]/', '', $text);
// 移除多余空格
$text = preg_replace('/\s+/', ' ', $text);
return trim($text);
}
/**
* 获取词频向量
*/
private function getWordFrequency($text) {
$words = str_word_count($this->preprocess($text), 1);
return array_count_values($words);
}
private function cosineSimilarity($text1, $text2) {
$vector1 = $this->getWordFrequency($text1);
$vector2 = $this->getWordFrequency($text2);
// 简化实现,使用前述的cosineSimilarity函数
// ...
}
private function jaccardSimilarity($text1, $text2) {
// 简化实现
// ...
}
private function levenshteinSimilarity($text1, $text2) {
$distance = levenshtein($text1, $text2);
$maxLen = max(strlen($text1), strlen($text2));
return 1 - ($distance / $maxLen);
}
}
// 使用示例
$similarity = new TextSimilarity();
$text1 = "Natural language processing is fascinating";
$text2 = "Natural language processing is interesting";
echo "余弦相似度: " . $similarity->calculateSimilarity($text1, $text2, 'cosine');
性能优化建议
<?php
// 缓存文本向量
class VectorCache {
private $cache = [];
public function getVector($text) {
$hash = md5($text);
if (!isset($this->cache[$hash])) {
$this->cache[$hash] = $this->buildVector($text);
}
return $this->cache[$hash];
}
private function buildVector($text) {
// 构建向量逻辑
// ...
}
}
选择合适的算法
- 简短文本(如用户名):莱文斯坦距离
- 中等长度文本(如句子):余弦相似度 或 Jaccard
- 长文本比较 (如文章):TF-IDF + 余弦相似度
- 商品、产品名称:similar_text
注意事项
- 中文支持:上述方法主要针对英文,中文需要分词
- 性能:大量文本比较时考虑缓存和索引
- 精度:根据具体场景选择合适的方法
- 内存:处理大规模数据时注意内存使用
如果处理中文文本,建议使用 jieba-php 等分词库进行预处理。