在电商与阅读平台中,传统协同过滤推荐系统常面临 “冷启动”“兴趣漂移”“特征单一” 三大痛点。基于深度学习的图书推荐系统,通过融合文本特征(图书摘要、评论)、用户行为特征(浏览、购买、收藏)与时序特征(阅读时段、更新频率),能大幅提升推荐精准度。本文结合 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>

Logo

腾讯云面向开发者汇聚海量精品云计算使用和开发经验,营造开放的云计算技术生态圈。

更多推荐