PHP 基于内容的推荐

wen PHP项目 3

本文目录导读:

PHP 基于内容的推荐

  1. 数据库结构设计
  2. 内容特征提取与相似度计算类
  3. 推荐引擎核心类
  4. 使用示例
  5. 优化建议

我将为您创建一个完整的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;
}

的推荐系统会根据用户的评分行为构建用户画像,然后计算物品间的相似度来推荐最相关的内容,并提供推荐理由解释,您可以根据实际需求调整特征权重和相似度计算方式。

抱歉,评论功能暂时关闭!