本文目录导读:

我将为您创建一个完整的PHP基于内容的推荐系统,这个系统会根据物品的特征和用户的偏好来计算相似度,从而推荐最相关的内容。
数据库结构设计
-- 用户表
CREATE TABLE users (
id INT PRIMARY KEY AUTO_INCREMENT,
username VARCHAR(50) UNIQUE NOT NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
-- 物品表(以电影为例)
CREATE TABLE items (
id INT PRIMARY KEY AUTO_INCREMENT,VARCHAR(200) NOT NULL,
description TEXT,
genres VARCHAR(200), -- 类型,用逗号分隔
director VARCHAR(100),
actors TEXT, -- 演员列表,用逗号分隔
release_year INT,
rating DECIMAL(3,1),
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
-- 用户行为表(记录用户对物品的评分或浏览)
CREATE TABLE user_behavior (
id INT PRIMARY KEY AUTO_INCREMENT,
user_id INT,
item_id INT,
rating DECIMAL(2,1), -- 1-5分
behavior_type ENUM('view', 'rate', 'purchase', 'favorite') DEFAULT 'view',
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (user_id) REFERENCES users(id),
FOREIGN KEY (item_id) REFERENCES items(id)
);
-- 测试数据
INSERT INTO items (title, description, genres, director, actors, release_year, rating) VALUES
('Inception', 'A thief who steals corporate secrets through dream-sharing technology', 'Sci-Fi,Action,Thriller', 'Christopher Nolan', 'Leonardo DiCaprio,Joseph Gordon-Levitt,Ellen Page', 2010, 8.8),
('Interstellar', 'A team of explorers travel through a wormhole in space', 'Sci-Fi,Drama,Adventure', 'Christopher Nolan', 'Matthew McConaughey,Anne Hathaway', 2014, 8.6),
('The Dark Knight', 'Batman faces the Joker', 'Action,Crime,Drama', 'Christopher Nolan', 'Christian Bale,Heath Ledger', 2008, 9.0),
('Titanic', 'A love story on the ill-fated ship', 'Romance,Drama', 'James Cameron', 'Leonardo DiCaprio,Kate Winslet', 1997, 7.8),
('Avatar', 'A paraplegic marine dispatched to the moon Pandora', 'Action,Adventure,Sci-Fi', 'James Cameron', 'Sam Worthington,Zoe Saldana', 2009, 7.8);
内容特征提取与相似度计算类
<?php
class ContentBasedRecommender {
private $pdo;
private $userId;
private $items;
private $userProfile;
public function __construct($pdo, $userId = null) {
$this->pdo = $pdo;
$this->userId = $userId;
$this->loadItems();
}
// 加载所有物品
private function loadItems() {
$stmt = $this->pdo->query("SELECT * FROM items");
$this->items = $stmt->fetchAll(PDO::FETCH_ASSOC);
}
// 加载用户画像(基于用户历史行为)
public function buildUserProfile() {
if (!$this->userId) {
return null;
}
// 获取用户所有行为记录
$stmt = $this->pdo->prepare("
SELECT i.*, ub.rating as user_rating
FROM user_behavior ub
JOIN items i ON ub.item_id = i.id
WHERE ub.user_id = ? AND ub.rating > 0
");
$stmt->execute([$this->userId]);
$ratedItems = $stmt->fetchAll(PDO::FETCH_ASSOC);
if (empty($ratedItems)) {
return null;
}
// 构建用户特征向量
$profile = [
'genres' => [],
'directors' => [],
'actors' => [],
'avg_rating' => 0,
'preferred_year' => 0
];
$totalRating = 0;
$totalYear = 0;
$count = 0;
foreach ($ratedItems as $item) {
$weight = $item['user_rating']; // 评分作为权重
// 处理类型
if (!empty($item['genres'])) {
$genres = explode(',', $item['genres']);
foreach ($genres as $genre) {
$genre = trim($genre);
$profile['genres'][$genre] = ($profile['genres'][$genre] ?? 0) + $weight;
}
}
// 处理导演
if (!empty($item['director'])) {
$director = $item['director'];
$profile['directors'][$director] = ($profile['directors'][$director] ?? 0) + $weight;
}
// 处理演员(取前3个)
if (!empty($item['actors'])) {
$actors = explode(',', $item['actors']);
$actors = array_slice($actors, 0, 3);
foreach ($actors as $actor) {
$actor = trim($actor);
$profile['actors'][$actor] = ($profile['actors'][$actor] ?? 0) + $weight;
}
}
$totalRating += $item['user_rating'];
$totalYear += $item['release_year'];
$count++;
}
// 计算平均值和权重
$profile['avg_rating'] = $totalRating / $count;
$profile['preferred_year'] = $totalYear / $count;
// 归一化
foreach ($profile['genres'] as &$val) {
$val = $val / $totalRating;
}
foreach ($profile['directors'] as &$val) {
$val = $val / $totalRating;
}
foreach ($profile['actors'] as &$val) {
$val = $val / $totalRating;
}
return $profile;
}
// 计算物品之间的余弦相似度
public function calculateCosineSimilarity($item1, $item2) {
$features1 = $this->extractFeatures($item1);
$features2 = $this->extractFeatures($item2);
// 合并所有特征
$allFeatures = array_unique(array_merge(
array_keys($features1['genres']),
array_keys($features2['genres'])
));
$dotProduct = 0;
$norm1 = 0;
$norm2 = 0;
foreach ($allFeatures as $feature) {
$v1 = $features1['genres'][$feature] ?? 0;
$v2 = $features2['genres'][$feature] ?? 0;
$dotProduct += $v1 * $v2;
$norm1 += $v1 * $v1;
$norm2 += $v2 * $v2;
}
// 如果两个物品都没有特征,返回0
if ($norm1 == 0 || $norm2 == 0) {
return 0;
}
return $dotProduct / (sqrt($norm1) * sqrt($norm2));
}
// 计算物品与用户画像的相似度
public function calculateItemUserSimilarity($item, $userProfile) {
if (!$userProfile) {
return 0;
}
$score = 0;
$weights = [
'genre_weight' => 0.4,
'director_weight' => 0.2,
'actor_weight' => 0.2,
'rating_weight' => 0.1,
'year_weight' => 0.1
];
// 类型相似度
$itemGenres = explode(',', $item['genres'] ?? '');
$genreScore = 0;
foreach ($itemGenres as $genre) {
$genre = trim($genre);
if (isset($userProfile['genres'][$genre])) {
$genreScore += $userProfile['genres'][$genre];
}
}
$score += $genreScore * $weights['genre_weight'];
// 导演相似度
$directorScore = 0;
if (isset($userProfile['directors'][$item['director']])) {
$directorScore = $userProfile['directors'][$item['director']];
}
$score += $directorScore * $weights['director_weight'];
// 演员相似度
$actorScore = 0;
if (!empty($item['actors'])) {
$actors = explode(',', $item['actors']);
foreach ($actors as $actor) {
$actor = trim($actor);
if (isset($userProfile['actors'][$actor])) {
$actorScore += $userProfile['actors'][$actor];
}
}
}
$score += $actorScore * $weights['actor_weight'];
// 评分相似度(与用户平均评分比较)
if ($item['rating'] > 0 && $userProfile['avg_rating'] > 0) {
$ratingDiff = abs($item['rating'] - $userProfile['avg_rating']);
$ratingScore = max(0, 1 - ($ratingDiff / 5));
$score += $ratingScore * $weights['rating_weight'];
}
// 年份相似度
if ($item['release_year'] > 0 && $userProfile['preferred_year'] > 0) {
$yearDiff = abs($item['release_year'] - $userProfile['preferred_year']);
$yearScore = max(0, 1 - ($yearDiff / 20));
$score += $yearScore * $weights['year_weight'];
}
return $score;
}
// 提取物品特征向量
private function extractFeatures($item) {
return [
'genres' => $this->createFeatureVector($item['genres'] ?? ''),
'directors' => $item['director'] ? [$item['director'] => 1] : [],
'actors' => $this->createFeatureVector($item['actors'] ?? '')
];
}
// 创建特征向量
private function createFeatureVector($csv) {
$vector = [];
if (!empty($csv)) {
$items = explode(',', $csv);
foreach ($items as $item) {
$item = trim($item);
if (!empty($item)) {
$vector[$item] = 1;
}
}
}
return $vector;
}
// 获取推荐结果
public function getRecommendations($topN = 10) {
$userProfile = $this->buildUserProfile();
if (!$userProfile) {
return $this->getPopularItems($topN);
}
// 获取用户已拥有或已评分的物品
$excludeIds = $this->getUserBehaviorItems();
$recommendations = [];
foreach ($this->items as $item) {
if (in_array($item['id'], $excludeIds)) {
continue;
}
$score = $this->calculateItemUserSimilarity($item, $userProfile);
$recommendations[] = [
'item' => $item,
'score' => $score
];
}
// 按分数排序
usort($recommendations, function($a, $b) {
return $b['score'] <=> $a['score'];
});
// 返回前N个
return array_slice($recommendations, 0, $topN);
}
// 获取物品相似推荐(类似"喜欢这个的人也喜欢")
public function getSimilarItems($itemId, $topN = 5) {
$targetItem = null;
foreach ($this->items as $item) {
if ($item['id'] == $itemId) {
$targetItem = $item;
break;
}
}
if (!$targetItem) {
return [];
}
$similarItems = [];
foreach ($this->items as $item) {
if ($item['id'] == $itemId) {
continue;
}
$similarity = $this->calculateCosineSimilarity($targetItem, $item);
$similarItems[] = [
'item' => $item,
'similarity' => $similarity
];
}
usort($similarItems, function($a, $b) {
return $b['similarity'] <=> $a['similarity'];
});
return array_slice($similarItems, 0, $topN);
}
// 获取用户已交互的物品
private function getUserBehaviorItems() {
if (!$this->userId) {
return [];
}
$stmt = $this->pdo->prepare("SELECT item_id FROM user_behavior WHERE user_id = ?");
$stmt->execute([$this->userId]);
return $stmt->fetchAll(PDO::FETCH_COLUMN);
}
// 获取热门物品(无用户画像时)
public function getPopularItems($topN = 10) {
$stmt = $this->pdo->query("
SELECT i.*, COUNT(ub.id) as view_count
FROM items i
LEFT JOIN user_behavior ub ON i.id = ub.item_id
GROUP BY i.id
ORDER BY i.rating DESC, view_count DESC
LIMIT $topN
");
$popularItems = $stmt->fetchAll(PDO::FETCH_ASSOC);
return array_map(function($item) {
return ['item' => $item, 'score' => $item['rating']];
}, $popularItems);
}
// 获取物品的完整特征描述
public function getItemFeatures($itemId) {
foreach ($this->items as $item) {
if ($item['id'] == $itemId) {
return $this->extractFeatures($item);
}
}
return null;
}
// 获取解释推荐理由
public function getRecommendationExplanation($item, $userProfile) {
$reasons = [];
// 类型匹配
$itemGenres = explode(',', $item['genres']);
$matchedGenres = [];
foreach ($itemGenres as $genre) {
$genre = trim($genre);
if (isset($userProfile['genres'][$genre])) {
$matchedGenres[] = $genre;
}
}
if ($matchedGenres) {
$reasons[] = "你喜欢" . implode(',', $matchedGenres) . "类型";
}
// 导演匹配
if (isset($userProfile['directors'][$item['director']])) {
$reasons[] = "导演" . $item['director'] . "的作品";
}
// 演员匹配
$matchedActors = [];
$itemActors = explode(',', $item['actors']);
foreach ($itemActors as $actor) {
$actor = trim($actor);
if (isset($userProfile['actors'][$actor])) {
$matchedActors[] = $actor;
}
}
if ($matchedActors) {
$reasons[] = "有你喜欢的演员" . implode(',', $matchedActors);
}
// 评分推荐
if ($item['rating'] >= 7) {
$reasons[] = "评分高达" . $item['rating'] . "分";
}
return $reasons;
}
}
推荐引擎核心类
<?php
class RecommendationEngine {
private $pdo;
private $contentBased;
public function __construct($pdo) {
$this->pdo = $pdo;
$this->contentBased = new ContentBasedRecommender($pdo);
}
// 为指定用户生成推荐
public function recommendForUser($userId, $topN = 10) {
$recommender = new ContentBasedRecommender($this->pdo, $userId);
$recommendations = $recommender->getRecommendations($topN);
$results = [];
foreach ($recommendations as $rec) {
$item = $rec['item'];
$userProfile = $recommender->buildUserProfile();
$reasons = $recommender->getRecommendationExplanation($item, $userProfile);
$results[] = [
'item' => $item,
'score' => round($rec['score'], 3),
'reasons' => $reasons,
'reason_text' => implode(',', $reasons)
];
}
return $results;
}
// 批量为所有用户生成推荐
public function recommendForAllUsers($topN = 10) {
$stmt = $this->pdo->query("SELECT id FROM users");
$users = $stmt->fetchAll(PDO::FETCH_COLUMN);
$allRecommendations = [];
foreach ($users as $userId) {
$allRecommendations[$userId] = $this->recommendForUser($userId, $topN);
}
return $allRecommendations;
}
// 生成推荐报告
public function generateReport($userId = null) {
$recommender = new ContentBasedRecommender($this->pdo, $userId);
$report = [
'user_id' => $userId,
'items_count' => count($this->getAllItems()),
'users_count' => count($this->getAllUsers()),
'recommendations' => []
];
if ($userId) {
$report['recommendations'] = $this->recommendForUser($userId);
$report['user_profile'] = $recommender->buildUserProfile();
} else {
$report['recommendations'] = $this->recommendForAllUsers();
}
return $report;
}
private function getAllItems() {
$stmt = $this->pdo->query("SELECT COUNT(*) FROM items");
return $stmt->fetchColumn();
}
private function getAllUsers() {
$stmt = $this->pdo->query("SELECT COUNT(*) FROM users");
return $stmt->fetchColumn();
}
}
使用示例
<?php
// 数据库连接
$pdo = new PDO('mysql:host=localhost;dbname=recommendation', 'username', 'password');
$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
// 初始化推荐引擎
$engine = new RecommendationEngine($pdo);
// 为用户1生成推荐
$userId = 1;
$recommendations = $engine->recommendForUser($userId, 10);
echo "<h2>基于内容的推荐结果</h2>";
echo "<table border='1'>";
echo "<tr><th>标题</th><th>类型</th><th>评分</th><th>推荐理由</th><th>匹配度</th></tr>";
foreach ($recommendations as $rec) {
echo "<tr>";
echo "<td>" . $rec['item']['title'] . "</td>";
echo "<td>" . $rec['item']['genres'] . "</td>";
echo "<td>" . $rec['item']['rating'] . "</td>";
echo "<td>" . $rec['reason_text'] . "</td>";
echo "<td>" . $rec['score'] . "</td>";
echo "</tr>";
}
echo "</table>";
// 获取相似物品推荐
echo "<h2>相似物品推荐</h2>";
$itemId = 1; // Inception
$similarItems = $engine->contentBased->getSimilarItems($itemId);
foreach ($similarItems as $rec) {
echo $rec['item']['title'] . " - 相似度: " . round($rec['similarity'], 2) . "<br>";
}
优化建议
1 缓存优化
// 添加Redis缓存
class CachedContentBasedRecommender extends ContentBasedRecommender {
private $cache;
public function __construct($pdo, $userId = null) {
parent::__construct($pdo, $userId);
$this->cache = new Redis();
$this->cache->connect('127.0.0.1', 6379);
}
public function getRecommendations($topN = 10) {
$cacheKey = "recommendations:user:{$this->userId}:top:{$topN}";
if ($cached = $this->cache->get($cacheKey)) {
return unserialize($cached);
}
$recommendations = parent::getRecommendations($topN);
$this->cache->setex($cacheKey, 3600, serialize($recommendations));
return $recommendations;
}
}
2 特征权重调优
// 可以根据用户行为动态调整权重
public function adjustWeights($userBehavior) {
// 分析用户行为模式
$weights = [
'genre_weight' => 0.4,
'director_weight' => 0.2,
'actor_weight' => 0.2,
'rating_weight' => 0.1,
'year_weight' => 0.1
];
// 根据行为类型调整
if ($userBehavior['purchase_count'] > 0) {
$weights['genre_weight'] += 0.1;
}
return $weights;
}
的推荐系统会根据用户的评分行为构建用户画像,然后计算物品间的相似度来推荐最相关的内容,并提供推荐理由解释,您可以根据实际需求调整特征权重和相似度计算方式。