本文目录导读:

在PHP中实现余弦相似度主要涉及文本向量化和数学计算两个核心步骤,以下是完整的实现方案:
基础余弦相似度函数
<?php
/**
* 计算两个向量的余弦相似度
* @param array $vectorA 向量A
* @param array $vectorB 向量B
* @return float 余弦相似度 (-1到1)
*/
function cosineSimilarity(array $vectorA, array $vectorB): float {
// 确保两个向量长度相同
if (count($vectorA) !== count($vectorB)) {
throw new InvalidArgumentException('向量维度必须相同');
}
$dotProduct = 0;
$normA = 0;
$normB = 0;
foreach ($vectorA as $i => $valueA) {
$valueB = $vectorB[$i];
$dotProduct += $valueA * $valueB;
$normA += $valueA * $valueA;
$normB += $valueB * $valueB;
}
$normA = sqrt($normA);
$normB = sqrt($normB);
// 防止除以0
if ($normA == 0 || $normB == 0) {
return 0.0;
}
return $dotProduct / ($normA * $normB);
}
文本向量化实现
1 简单词频向量化
<?php
/**
* 将文本转为词频向量
* @param string $text 输入文本
* @param array $vocabulary 词汇表(可选)
* @return array 词频向量
*/
function textToVector(string $text, array $vocabulary = []): array {
// 分词(简单实现,可根据需求改进)
$words = preg_split('/\s+/', mb_strtolower($text));
// 去除标点符号
$words = array_map(function($word) {
return preg_replace('/[^\w\s]/u', '', $word);
}, $words);
// 过滤空词
$words = array_filter($words);
// 统计词频
$wordFreq = array_count_values($words);
// 如果没有提供词汇表,使用文本中的词构建
if (empty($vocabulary)) {
$vocabulary = array_keys($wordFreq);
}
// 构建向量
$vector = [];
foreach ($vocabulary as $word) {
$vector[] = $wordFreq[$word] ?? 0;
}
return $vector;
}
/**
* 计算两段文本的余弦相似度
*/
function textCosineSimilarity(string $textA, string $textB): float {
// 构建联合词汇表
$wordsA = array_keys(array_count_values(
array_filter(preg_split('/\s+/', mb_strtolower($textA)))
));
$wordsB = array_keys(array_count_values(
array_filter(preg_split('/\s+/', mb_strtolower($textB)))
));
$vocabulary = array_unique(array_merge($wordsA, $wordsB));
// 向量化
$vectorA = textToVector($textA, $vocabulary);
$vectorB = textToVector($textB, $vocabulary);
return cosineSimilarity($vectorA, $vectorB);
}
2 TF-IDF向量化(更精确)
<?php
class TFIDFSimilarity {
private array $documents;
private array $vocabulary = [];
private array $idfValues = [];
public function __construct(array $documents) {
$this->documents = $documents;
$this->buildVocabulary();
$this->calculateIDF();
}
private function buildVocabulary(): void {
$allWords = [];
foreach ($this->documents as $doc) {
$words = $this->tokenize($doc);
$allWords = array_merge($allWords, $words);
}
$this->vocabulary = array_unique($allWords);
}
private function tokenize(string $text): array {
$text = mb_strtolower($text);
$words = preg_split('/\s+/', $text);
return array_filter($words, function($word) {
return !empty(trim($word));
});
}
private function calculateIDF(): void {
$docCount = count($this->documents);
foreach ($this->vocabulary as $word) {
$docWithWord = 0;
foreach ($this->documents as $doc) {
$words = $this->tokenize($doc);
if (in_array($word, $words)) {
$docWithWord++;
}
}
// IDF = log(文档总数 / 包含该词的文档数)
$this->idfValues[$word] = log(($docCount + 1) / ($docWithWord + 1)) + 1;
}
}
public function textToTfidfVector(string $text): array {
$words = $this->tokenize($text);
$wordCount = count($words);
$vector = [];
foreach ($this->vocabulary as $word) {
$tf = empty($words) ? 0 : array_count_values($words)[$word] ?? 0;
$tf = $tf / $wordCount; // 归一化词频
$idf = $this->idfValues[$word] ?? 0;
$vector[] = $tf * $idf;
}
return $vector;
}
public function calculateSimilarity(string $textA, string $textB): float {
$vectorA = $this->textToTfidfVector($textA);
$vectorB = $this->textToTfidfVector($textB);
return cosineSimilarity($vectorA, $vectorB);
}
}
完整使用示例
<?php
// 示例1:简单文本相似度比较
$text1 = "机器学习和深度学习是人工智能的核心技术";
$text2 = "深度学习在自然语言处理中有广泛应用";
$similarity = textCosineSimilarity($text1, $text2);
echo "文本相似度: " . round($similarity, 4) . PHP_EOL;
// 示例2:使用TF-IDF
$documents = [
"人工智能可以模拟人类智能",
"机器学习是人工智能的重要分支",
"深度学习让机器学习更加强大",
"自然语言处理需要深度学习技术",
"Python是机器学习常用的编程语言"
];
$tfidf = new TFIDFSimilarity($documents);
$query1 = "人工智能机器学习";
$query2 = "深度学习自然语言";
$similarity = $tfidf->calculateSimilarity($query1, $query2);
echo "TF-IDF相似度: " . round($similarity, 4) . PHP_EOL;
// 示例3:文档搜索
function searchSimilarDocuments(string $query, array $documents, TFIDFSimilarity $tfidf): array {
$results = [];
foreach ($documents as $index => $doc) {
$similarity = $tfidf->calculateSimilarity($query, $doc);
$results[] = [
'index' => $index,
'document' => $doc,
'similarity' => $similarity
];
}
usort($results, function($a, $b) {
return $b['similarity'] <=> $a['similarity'];
});
return $results;
}
$query = "人工智能技术";
$results = searchSimilarDocuments($query, $documents, $tfidf);
echo "搜索\"$query\"的结果:" . PHP_EOL;
foreach (array_slice($results, 0, 3) as $result) {
echo sprintf("相似度 %.4f: %s\n",
$result['similarity'],
$result['document']
);
}
性能优化建议
<?php
// 1. 使用缓存存储向量
class CachedVectorSimilarity {
private array $vectorCache = [];
private TFIDFSimilarity $tfidf;
public function __construct(TFIDFSimilarity $tfidf) {
$this->tfidf = $tfidf;
}
public function getVector(string $text): array {
$key = md5($text);
if (!isset($this->vectorCache[$key])) {
$this->vectorCache[$key] = $this->tfidf->textToTfidfVector($text);
}
return $this->vectorCache[$key];
}
}
// 2. 使用SplFixedArray提高性能
function optimizedCosineSimilarity(array $vectorA, array $vectorB): float {
$a = SplFixedArray::fromArray($vectorA);
$b = SplFixedArray::fromArray($vectorB);
$dotProduct = 0;
$normA = 0;
$normB = 0;
for ($i = 0; $i < $a->getSize(); $i++) {
$dotProduct += $a[$i] * $b[$i];
$normA += $a[$i] * $a[$i];
$normB += $b[$i] * $b[$i];
}
return $dotProduct / (sqrt($normA) * sqrt($normB));
}
高级实现:使用外部库
# 使用composer安装数学库 composer require markrogoyski/math-php
<?php
use MathPHP\LinearAlgebra\Vector;
function cosineSimilarityWithLibrary(array $vectorA, array $vectorB): float {
$v1 = new Vector($vectorA);
$v2 = new Vector($vectorB);
return $v1->dotProduct($v2) / ($v1->l2Norm() * $v2->l2Norm());
}
注意事项
- 中文分词:中文文本需要先分词,建议使用
jieba-php或scws等分词库 - 停用词处理:过滤常见的无意义词(的、了、是等)
- 向量维度:词汇表大小直接影响性能和内存使用
- 精度问题:使用
float类型时注意精度损失
这个实现提供了从基础到高级的多种方案,可以根据具体需求选择合适的实现方式。