程序员实战:基于深度学习的图书推荐系统设计与实现(附代码)
本文提出基于深度学习的图书推荐系统解决方案,针对传统协同过滤的冷启动、兴趣漂移等问题,通过融合文本特征(TF-IDF编码)、用户行为序列(GRU建模)和双塔模型实现精准推荐。技术实现包含:1)数据预处理(特征工程、正负样本生成);2)CNN模型提取图书摘要深层特征;3)GRU捕捉用户行为时序模式;4)双塔模型高效匹配用户-图书特征。实验表明,该方案能有效提升推荐准确率,为工业级图书推荐系统提供完整
在电商与阅读平台中,传统协同过滤推荐系统常面临 “冷启动”“兴趣漂移”“特征单一” 三大痛点。基于深度学习的图书推荐系统,通过融合文本特征(图书摘要、评论)、用户行为特征(浏览、购买、收藏)与时序特征(阅读时段、更新频率),能大幅提升推荐精准度。本文结合 Python 实战代码,从数据预处理、模型构建(含 CNN、GRU、双塔模型)到系统部署,详解深度学习推荐系统的开发全流程,同时提供学术性设计思路(呼应 “LW” 需求),助力开发者快速落地工业级图书推荐方案。
数据预处理:构建高质量推荐数据集
高质量数据是深度学习推荐系统的基础。图书推荐场景的数据源通常包括 “用户表”“图书表”“行为表”,需通过清洗、特征工程、数据划分三个步骤,将原始数据转化为模型可输入的格式。本节重点解决 “文本特征编码”“行为序列处理”“数据不平衡” 问题,为后续模型训练提供支撑。
数据清洗与整合
首先整合多源数据,处理缺失值、异常值与重复数据,确保数据一致性:
import pandas as pd
import numpy as np
import re
from datetime import datetime
# 1. 加载原始数据(模拟电商平台图书推荐数据集)
users_df = pd.read_csv("data/users.csv") # 用户表:user_id, age, gender, city, registration_time
books_df = pd.read_csv("data/books.csv") # 图书表:book_id, title, author, category, summary, publish_time, price
behaviors_df = pd.read_csv("data/behaviors.csv") # 行为表:user_id, book_id, behavior_type, timestamp
# 2. 数据清洗:处理缺失值
# 用户表:填充缺失的城市信息为“未知”
users_df["city"] = users_df["city"].fillna("未知")
# 图书表:删除摘要为空的记录(摘要为核心文本特征)
books_df = books_df.dropna(subset=["summary"]).reset_index(drop=True)
# 行为表:过滤无效时间戳(确保在2020-2025年间)
behaviors_df["timestamp"] = pd.to_datetime(behaviors_df["timestamp"])
behaviors_df = behaviors_df[
(behaviors_df["timestamp"] >= "2020-01-01") &
(behaviors_df["timestamp"] <= "2025-01-01")
].reset_index(drop=True)
# 3. 数据整合:关联三张表,构建用户-图书-行为全量数据集
# 关联用户与行为数据
user_behavior_df = pd.merge(
behaviors_df, users_df, on="user_id", how="inner"
)
# 关联图书数据(仅保留存在的图书)
full_df = pd.merge(
user_behavior_df, books_df, on="book_id", how="inner"
)
# 4. 处理行为类型:将浏览(1)、收藏(2)、购买(3)映射为权重(体现兴趣强度)
behavior_weight = {1: 0.1, 2: 0.5, 3: 1.0}
full_df["behavior_weight"] = full_df["behavior_type"].map(behavior_weight)
# 5. 保存清洗后的数据
full_df.to_csv("data/cleaned_book_recommendation_data.csv", index=False)
print(f"数据清洗完成,最终数据集大小:{full_df.shape}")
print(f"用户数量:{full_df['user_id'].nunique()}")
print(f"图书数量:{full_df['book_id'].nunique()}")
特征工程(文本与行为特征编码)
图书推荐的核心特征包括 “图书文本特征”(摘要、标题、作者)与 “用户行为序列特征”,需通过编码转化为数值向量:
from sklearn.preprocessing import LabelEncoder, MinMaxScaler
from sklearn.feature_extraction.text import TfidfVectorizer
import torch
from torch.utils.data import Dataset
# 1. 分类特征编码(用户性别、城市、图书分类)
# 初始化编码器
user_gender_encoder = LabelEncoder()
user_city_encoder = LabelEncoder()
book_category_encoder = LabelEncoder()
# 编码用户特征
full_df["gender_encoded"] = user_gender_encoder.fit_transform(full_df["gender"])
full_df["city_encoded"] = user_city_encoder.fit_transform(full_df["city"])
# 编码图书特征
full_df["category_encoded"] = book_category_encoder.fit_transform(full_df["category"])
# 2. 数值特征归一化(用户年龄、图书价格、时间戳)
# 年龄归一化(0-1区间)
age_scaler = MinMaxScaler()
full_df["age_normalized"] = age_scaler.fit_transform(full_df[["age"]])
# 价格归一化
price_scaler = MinMaxScaler()
full_df["price_normalized"] = price_scaler.fit_transform(full_df[["price"]])
# 时间戳转化为“距离注册时间的天数”(体现用户活跃度)
full_df["registration_time"] = pd.to_datetime(full_df["registration_time"])
full_df["days_since_registration"] = (full_df["timestamp"] - full_df["registration_time"]).dt.days
# 归一化天数(0-1区间)
days_scaler = MinMaxScaler()
full_df["days_since_registration_normalized"] = days_scaler.fit_transform(full_df[["days_since_registration"]])
# 3. 图书文本特征编码(基于TF-IDF的摘要编码,后续可替换为BERT)
# 文本预处理:去除特殊字符、小写化
def preprocess_text(text):
text = re.sub(r"[^a-zA-Z0-9\u4e00-\u9fa5\s]", "", text) # 保留中英文与数字
text = text.lower()
return text
full_df["summary_processed"] = full_df["summary"].apply(preprocess_text)
# TF-IDF编码(取前512个特征)
tfidf = TfidfVectorizer(max_features=512, stop_words="english") # 英文停用词(可添加中文停用词)
summary_tfidf = tfidf.fit_transform(full_df["summary_processed"]).toarray()
# 转化为DataFrame并合并到全量数据
summary_tfidf_df = pd.DataFrame(
summary_tfidf,
columns=[f"summary_tfidf_{i}" for i in range(512)]
)
full_df = pd.concat([full_df, summary_tfidf_df], axis=1)
# 4. 构建用户-图书行为序列(用于后续时序模型)
def build_user_behavior_sequence(full_df):
"""构建用户的图书行为序列(按时间排序)"""
user_sequences = {}
# 按用户分组,每个用户的行为按时间升序排列
grouped = full_df.sort_values("timestamp").groupby("user_id")
for user_id, group in grouped:
# 序列包含:book_id、behavior_weight、category_encoded
sequence = group[["book_id", "behavior_weight", "category_encoded"]].values.tolist()
user_sequences[user_id] = sequence
return user_sequences
user_behavior_sequences = build_user_behavior_sequence(full_df)
# 保存序列数据(后续GRU模型使用)
np.save("data/user_behavior_sequences.npy", user_behavior_sequences)
# 5. 数据划分(训练集80%,测试集20%,按时间划分避免数据泄露)
train_df = full_df[full_df["timestamp"] < "2024-09-01"].copy()
test_df = full_df[full_df["timestamp"] >= "2024-09-01"].copy()
print(f"训练集大小:{train_df.shape},测试集大小:{test_df.shape}")
train_df.to_csv("data/train_data.csv", index=False)
test_df.to_csv("data/test_data.csv", index=False)
构建推荐系统数据集(正负样本生成)
推荐系统通常采用 “正负样本对比学习”,需为每个用户生成 “正样本”(有行为的图书)与 “负样本”(无行为的图书):
def generate_pos_neg_samples(df, all_book_ids, neg_sample_ratio=3):
"""
生成正负样本
:param df: 输入数据(训练/测试集)
:param all_book_ids: 所有图书ID列表(用于负采样)
:param neg_sample_ratio: 负样本比例(正样本:负样本=1:neg_sample_ratio)
:return: 正负样本数据集
"""
# 1. 提取正样本(用户有行为的(user_id, book_id)对)
pos_samples = df[["user_id", "book_id"]].drop_duplicates()
pos_samples["label"] = 1 # 正样本标签为1
# 2. 负采样(为每个用户随机选择无行为的图书)
neg_samples = []
user_ids = df["user_id"].unique()
for user_id in user_ids:
# 用户有行为的图书ID
user_pos_books = df[df["user_id"] == user_id]["book_id"].unique()
# 从所有图书中排除正样本,作为负样本候选
user_neg_candidates = [bid for bid in all_book_ids if bid not in user_pos_books]
# 随机选择负样本(数量=正样本数量*neg_sample_ratio)
pos_count = len(user_pos_books)
neg_count = pos_count * neg_sample_ratio
# 若候选不足,按实际数量选择
neg_books = np.random.choice(user_neg_candidates, size=min(neg_count, len(user_neg_candidates)), replace=False)
# 添加到负样本列表
neg_samples.extend([(user_id, book_id, 0) for book_id in neg_books])
# 3. 合并正负样本
neg_samples_df = pd.DataFrame(neg_samples, columns=["user_id", "book_id", "label"])
sample_df = pd.concat([pos_samples, neg_samples_df], ignore_index=True)
# 4. 合并特征数据(用户特征、图书特征)
# 提取用户特征(去重)
user_features = df[["user_id", "gender_encoded", "city_encoded", "age_normalized", "days_since_registration_normalized"]].drop_duplicates()
# 提取图书特征(去重)
book_features = df[["book_id", "category_encoded", "price_normalized"] + [f"summary_tfidf_{i}" for i in range(512)]].drop_duplicates()
# 合并特征
sample_df = sample_df.merge(user_features, on="user_id", how="left")
sample_df = sample_df.merge(book_features, on="book_id", how="left")
return sample_df
# 所有图书ID列表
all_book_ids = full_df["book_id"].unique()
# 生成训练集与测试集的正负样本
train_sample_df = generate_pos_neg_samples(train_df, all_book_ids, neg_sample_ratio=3)
test_sample_df = generate_pos_neg_samples(test_df, all_book_ids, neg_sample_ratio=3)
print(f"训练集正负样本分布:正样本{train_sample_df[train_sample_df['label']==1].shape[0]}个,负样本{train_sample_df[train_sample_df['label']==0].shape[0]}个")
print(f"测试集正负样本分布:正样本{test_sample_df[test_sample_df['label']==1].shape[0]}个,负样本{test_sample_df[test_sample_df['label']==0].shape[0]}个")
# 保存正负样本数据(用于模型训练)
train_sample_df.to_csv("data/train_samples.csv", index=False)
test_sample_df.to_csv("data/test_samples.csv", index=False)
模型构建:深度学习推荐模型实现
基于图书推荐场景的特点,本节实现三种主流深度学习模型:CNN 文本特征模型(提取图书摘要深层特征)、GRU 时序模型(捕捉用户行为序列趋势)、双塔模型(工业级推荐常用,兼顾效率与精度),并通过代码对比各模型性能。
CNN 图书文本特征模型(提取摘要深层特征)
图书摘要包含丰富的内容信息,CNN 能有效捕捉文本中的局部关键特征(如主题词、风格描述),为推荐提供内容维度支撑:
import torch.nn as nn
import torch.nn.functional as F
from torch.utils.data import DataLoader, TensorDataset
# 1. 定义CNN文本特征提取模型
class BookCNNSummaryModel(nn.Module):
def __init__(self, input_dim=512, num_filters=128, kernel_sizes=[3, 5, 7], output_dim=64):
"""
:param input_dim: 文本特征输入维度(TF-IDF为512)
:param num_filters: 卷积核数量
:param kernel_sizes: 不同尺寸的卷积核(捕捉不同长度的文本片段)
:param output_dim: 文本特征输出维度(最终图书文本嵌入维度)
"""
super(BookCNNSummaryModel, self).__init__()
# 卷积层(多尺度)
self.convs = nn.ModuleList([
nn.Conv1d(
in_channels=1, # 文本特征为1维序列
out_channels=num_filters,
kernel_size=ks,
padding=ks//2 # 保持输出长度与输入一致
) for ks in kernel_sizes
])
# 全连接层(将多尺度卷积特征映射到统一维度)
self.fc = nn.Linear(len(kernel_sizes)*num_filters, output_dim)
# dropout层(防止过拟合)
self.dropout = nn.Dropout(0.5)
def forward(self, x):
# x: [batch_size, input_dim] → 调整为卷积输入格式:[batch_size, 1, input_dim]
x = x.unsqueeze(1)
# 多尺度卷积+ReLU激活:每个卷积层输出[batch_size, num_filters, input_dim]
conv_outputs = [F.relu(conv(x)) for conv in self.convs]
# 全局最大池化:每个卷积层输出[batch_size, num_filters](提取每个卷积核的最大特征)
pooled_outputs = [F.adaptive_max_pool1d(conv_out, output_size=1).squeeze(2) for conv_out in conv_outputs]
# 拼接多尺度特征:[batch_size, len(kernel_sizes)*num_filters]
cat_outputs = self.dropout(torch.cat(pooled_outputs, dim=1))
# 全连接层输出最终文本特征:[batch_size, output_dim]
book_text_emb = self.fc(cat_outputs)
return book_text_emb
# 2. 数据加载(将CSV数据转为Tensor)
def load_tensor_data(sample_df, device):
# 提取特征列(用户特征+图书特征)
user_feature_cols = ["gender_encoded", "city_encoded", "age_normalized", "days_since_registration_normalized"]
book_feature_cols = ["category_encoded", "price_normalized"] + [f"summary_tfidf_{i}" for i in range(512)]
feature_cols = user_feature_cols + book_feature_cols
# 转为Tensor
X = torch.tensor(sample_df[feature_cols].values, dtype=torch.float32).to(device)
y = torch.tensor(sample_df["label"].values, dtype=torch.float32).to(device)
return TensorDataset(X, y)
# 3. 模型训练与评估(单独训练CNN文本特征模型,后续可融入双塔模型)
def train_evaluate_cnn_model(train_sample_df, test_sample_df, device):
# 超参数设置
input_dim = 512 # 文本特征维度
num_filters = 128
kernel_sizes = [3, 5, 7]
output_dim = 64
batch_size = 128
epochs = 10
lr = 1e-3
# 加载数据
train_dataset = load_tensor_data(train_sample_df, device)
test_dataset = load_tensor_data(test_sample_df, device)
train_loader = DataLoader(train_dataset, batch_size=batch_size, shuffle=True)
test_loader = DataLoader(test_dataset, batch_size=batch_size, shuffle=False)
# 初始化模型、损失函数、优化器
model = BookCNNSummaryModel(input_dim, num_filters, kernel_sizes, output_dim).to(device)
criterion = nn.BCEWithLogitsLoss() # 二分类交叉熵损失(推荐场景常用)
optimizer = torch.optim.Adam(model.parameters(), lr=lr)
# 训练</doubaocanvas>
更多推荐
所有评论(0)