深度学习算法说明
1、简介
神经网络协同过滤模型(NCF)
为了解决启发式推荐算法的问题,基于神经网络的协同过滤算法诞生了,神经网络的协同过滤算法可以
通过将用户和物品的特征向量作为输入,来预测用户对新物品的评分,从而解决冷启动问题。
对数据稀疏性的鲁棒性:神经网络的协同过滤算法可以自动学习用户和物品的特征向量,并能够通过这
些向量来预测评分,因此对于数据稀疏的情况也能进行有效的预测。
更好的预测准确率:神经网络的协同过滤算法可以通过多层非线性变换来学习用户和物品之间的复杂关
系,从而能够提高预测准确率。
可解释性和灵活性:神经网络的协同过滤算法可以通过调整网络结构和参数来优化预测准确率,并且可
以通过可视化方法来解释预测结果。
所以基于神经网络协同过滤模型是目前推荐系统的主流形态。
2、安装库
pip install numpy
pip install pandas
pip install tensorflow
3、流程
1、构造数据矩阵(从用户评分表中加载所有用户的评分数据。)
2、数据预处理:把数据向量化,便于神经网络计算
3、对数据进行打标签操作
4、定义64个维度针对向量进行处理(神经网络是非线性多维度)
5、创建NCF模型
6、合并 embeddings向量
7、添加全连接层
8、编译模型
9、模型评估
10、模型保存
4、代码
# -*-coding:utf-8-*-
"""
@contact: 微信 1257309054
@file: 深度学习推荐算法.py
@time: 2025/3/17 21:30
@author: LDC
"""
import os
import django
from django.conf import settings
os.environ["DJANGO_SETTINGS_MODULE"] = "finance_manager.settings"
django.setup()
import joblib
import matplotlib.pyplot as plt
import pymysql
import numpy as np
import pandas as pd
from sklearn.model_selection import train_test_split
from sklearn.preprocessing import LabelEncoder
from sklearn.metrics import classification_report
import tensorflow as tf
from keras.layers import Input, Embedding, Flatten, Dense, Concatenate
from keras.models import Model
from keras.src.layers import Dropout
from finance.models import UserSelectTypes, LikeRecommendfinance, Finances, RateFinance
# normalized for 中文显示和负号
plt.rcParams['font.sans-serif'] = ['SimHei']
plt.rcParams['axes.unicode_minus'] = False
def get_data():
'''
从数据库获取数据
'''
conn = pymysql.connect(host=settings.DATABASE_HOST,
user=settings.DATABASE_USER,
password=settings.DATABASE_PASS,
database=settings.DATABASE_NAME,
charset='utf8mb4',
use_unicode=True)
sql_cmd = 'SELECT user_id, finance_id, mark FROM rate_finance'
dataset = pd.read_sql(sql=sql_cmd, con=conn)
conn.close() # 使用完后记得关掉
return dataset
# ==================== 数据生成与预处理 ====================
def generate_data():
"""获取数据集"""
np.random.seed(42)
df = get_data()
df, finance_map_dict, user_id_map_dict = preprocessing(df) # 数据预处理
# 转换为二分类问题(假设>=3分为正样本)
df['label'] = (df['mark'] >= 3).astype(int)
n_users = len(df.user_id.unique()) # 统计用户数量
n_finances = len(df.finance_id.unique()) # 统计产品数量
num_samples = len(df) # 统计总样本数量
return df, n_users, n_finances, num_samples
def preprocess_data(df):
"""数据预处理"""
# 编码类别特征
user_encoder = LabelEncoder()
product_encoder = LabelEncoder()
df['user_encoded'] = user_encoder.fit_transform(df['user_id'])
df['product_encoded'] = product_encoder.fit_transform(df['finance_id'])
# 划分特征和标签
X = df[['user_encoded', 'product_encoded']]
y = df['label']
# 分割数据集
X_train, X_test, y_train, y_test = train_test_split(
X, y,
test_size=0.2,
random_state=42,
stratify=y # 保持类别分布
)
return X_train, X_test, y_train, y_test
# ==================== 模型构建 ====================
def build_model(num_users, num_products, embedding_dim, dropout_rate):
"""构建深度学习模型"""
# 输入层
user_input = Input(shape=(1,), name='user_input')
product_input = Input(shape=(1,), name='product_input')
# 嵌入层
user_embedding = Embedding(
input_dim=num_users + 1,
output_dim=embedding_dim,
name='user_embedding'
)(user_input)
product_embedding = Embedding(
input_dim=num_products + 1,
output_dim=embedding_dim,
name='product_embedding'
)(product_input)
# 合并特征
merged = Concatenate()([
Flatten()(user_embedding),
Flatten()(product_embedding)
])
# 全连接层
dense = Dense(128, activation='relu')(merged)
dense = Dropout(dropout_rate)(dense)
dense = Dense(64, activation='relu')(dense)
dense = Dropout(dropout_rate)(dense)
# 输出层
output = Dense(1, activation='sigmoid')(dense)
# 构建模型
model = Model(inputs=[user_input, product_input], outputs=output)
return model
# ==================== 主程序 ====================
def main():
# ==================== 配置参数 ====================
# 模型参数
embedding_dim = 64 # 嵌入维度
dropout_rate = 0.3 # 防止过拟合
THRESHOLD = 0.5 # 分类阈值
# 训练参数
EPOCHS = 15 # 训练轮数
BATCH_SIZE = 128
VALIDATION_SPLIT = 0.1
# 生成并预处理数据
df, num_users, num_products, num_samples = generate_data()
X_train, X_test, y_train, y_test = preprocess_data(df)
# 构建模型
model = build_model(num_users, num_products, embedding_dim, dropout_rate)
# 编译模型
model.compile(
optimizer=tf.keras.optimizers.Adam(learning_rate=0.001),
loss='binary_crossentropy',
metrics=[
'accuracy',
tf.keras.metrics.Precision(name='precision'),
tf.keras.metrics.Recall(name='recall'),
]
)
# 处理类别不平衡
class_weights = {
0: (1 / (len(y_train) - sum(y_train))) * (len(y_train) / 2.0),
1: (1 / sum(y_train)) * (len(y_train) / 2.0)
}
# 训练模型
history = model.fit(
[X_train['user_encoded'], X_train['product_encoded']],
y_train,
epochs=EPOCHS,
batch_size=BATCH_SIZE,
validation_split=VALIDATION_SPLIT,
class_weight=class_weights,
verbose=1
)
# 模型评估
print("\n模型评估结果:")
y_pred = (model.predict([X_test['user_encoded'], X_test['product_encoded']]) > THRESHOLD).astype(int)
report = classification_report(y_test, y_pred, output_dict=True, target_names=['低评分', '高评分'])
weighted_avg = report['weighted avg']
accuracy = round(report['accuracy'], 3) # 准确率
precision = round(weighted_avg['precision'], 3) # 精准度
recall = round(weighted_avg['recall'], 3) # 召回率
f1_score = round(weighted_avg['f1-score'], 3) # F1-score
print(report)
print('准确率是{},精准度是{},召回率是{},F1值是{}'.format(accuracy, precision, recall, f1_score))
# 训练过程可视化
plt.figure(figsize=(15, 6))
# 损失曲线
plt.subplot(1, 1, 1)
plt.plot(history.history['loss'], label='训练集损失')
plt.plot(history.history['val_loss'], label='验证集损失')
plt.title('损失变化曲线')
plt.xlabel('Epoch')
plt.ylabel('Loss')
plt.legend()
plt.show()
# # 指标曲线
# plt.subplot(1, 2, 2)
# for metric in ['precision', 'recall']:
# plt.plot(history.history[metric], label=f'训练集 {metric}')
# plt.plot(history.history[f'val_{metric}'], linestyle='--', label=f'验证集 {metric}')
# plt.title('指标变化曲线')
# plt.xlabel('Epoch')
# plt.ylabel('Score')
# plt.legend()
# plt.tight_layout()
# plt.show()
x = ['准确率', '精确度', '召回率', 'f1_score']
plt.subplot(1, 1, 1)
plt.title('模型指标')
plt.plot(x, [accuracy, precision, recall, f1_score], c='blue', marker='o', linestyle=':', label='深度学习')
# 图例展示位置,数字代表第几象限
plt.legend(loc=4)
plt.show()
# 保存模型
joblib.dump(model, 'user_product_model.h5')
# model.save('user_product_model.h5')
print("模型已保存为 user_product_model.h5")
def predict(user_id, dataset):
'''
将预测评分高的产品推荐给该用户user_id
'''
try:
# model = load_model('user_product_model.h5')
model = joblib.load('user_product_model.h5')
'''
先拿到所有的产品索引ISBN,并去重成为finance_data。
再添加一个和finance_data长度相等的用户列表user,不过这里的user列表中的元素全是1,
因为我们要做的是:预测第1个用户对所有产品的评分,再将预测评分高的产品推荐给该用户。
'''
finance_data = np.array(list(set(dataset.finance_id)))
user = np.array([user_id for i in range(len(finance_data))])
predictions = model.predict([user, finance_data])
# 更换列->行
predictions = np.array([a[0] for a in predictions])
# 根据原array,取其中数值从大到小的索引,再只取前top10
recommended_finance_ids = (-predictions).argsort()[:10]
print(recommended_finance_ids)
print(predictions[recommended_finance_ids])
return recommended_finance_ids
except Exception as e:
print('预测报错', e)
return []
def get_select_tag_finance(user_id, finance_id=None):
# 获取用户注册时选择的产品类别各返回10门产品
category_ids = []
us = UserSelectTypes.objects.get(user_id=user_id)
for category in us.category.all():
category_ids.append(category.id)
unlike_finance_ids = [d['finance_id'] for d in
LikeRecommendfinance.objects.filter(user_id=user_id, is_like=0).values('finance_id')]
if finance_id and finance_id not in unlike_finance_ids:
unlike_finance_ids.append(finance_id)
finance_list = Finances.objects.filter(category__in=category_ids).exclude(
id__in=unlike_finance_ids).distinct().order_by(
"-like_num")[:10]
return finance_list
def preprocessing(dataset):
'''
数据预处理
把评分数据映射成用户字典,产品字典
'''
finance_val_counts = dataset.finance_id.value_counts()
finance_map_dict = {}
for i in range(len(finance_val_counts)):
finance_map_dict[finance_val_counts.index[i]] = i
# print(map_dict)
dataset["finance_id"] = dataset["finance_id"].map(finance_map_dict)
user_id_val_counts = dataset.user_id.value_counts()
# 映射字典
user_id_map_dict = {}
for i in range(len(user_id_val_counts)):
user_id_map_dict[user_id_val_counts.index[i]] = i
# 将User_ID映射到一串字典
dataset["user_id"] = dataset["user_id"].map(user_id_map_dict)
return dataset, finance_map_dict, user_id_map_dict
def embedding_main(user_id, finance_id=None, is_rec_list=False):
'''
1、获取用户评分大于等于3的产品数据
2、数据预处理:把数据映射成用户向量Embedding,产品向量Embedding
3、划分训练集与测试集:使用二八法则随机划分,80%的数据用来训练,20%的数据用来测试
4、训练模型:分别Emmbeding两个向量,再Concat连接起来,最后加上3个全连接层构成模型,进行训练
5、模型评估:通过查看训练集损失函数来查看模型优劣
6、预测推荐:对用户评分过的产品进行模型预测,把预测评分高的产品推荐给用户
user_id: 用户id
finance_id: 用户已经评分过的产品id,需要在推荐列表中去除
is_rec_list: 值为True:返回推荐[用户-评分]列表,值为False:返回推荐的产品列表
'''
dataset = get_data() # 获取数据
# print(dataset.head())
if user_id not in dataset.user_id.unique():
# 用户未进行评分则推荐注册时选择的产品类型
print('用户未进行评分则推荐注册时选择的产品类型')
if is_rec_list:
return []
# 推荐列表为空,按用户注册时选择的产品类别各返回10门
return get_select_tag_finance(user_id, finance_id)
dataset, finance_map_dict, user_id_map_dict = preprocessing(dataset)
# user_id需要转换为映射后的user_id传到predict函数中
predict_finance_ids = predict(user_id_map_dict[user_id], dataset) # 预测的产品Id
recommend_list = [] # 最后推荐的产品id
# 把映射的值转为真正的产品id
for finance_id in predict_finance_ids:
for k, v in finance_map_dict.items():
if finance_id == v:
recommend_list.append(k)
print('keras_recommended_finance_ids深度学习推荐列表', recommend_list)
if not recommend_list:
# 推荐列表为空,且is_rec_list: 值为True:返回推荐[用户-评分]列表
if is_rec_list:
return []
# 推荐列表为空,按用户注册时选择的产品类别
return get_select_tag_finance(user_id, finance_id)
if is_rec_list:
# 推荐列表不为空,且且is_rec_list: 值为True:返回推荐[用户-评分]列表
return recommend_list
# 过滤掉用户反馈过不喜欢的产品
unlike_finance_ids = [d['finance_id'] for d in
LikeRecommendfinance.objects.filter(user_id=user_id, is_like=0).values('finance_id')]
# 过滤掉用户已评分的数据
already_mark_ids = [d['finance_id'] for d in RateFinance.objects.filter(user_id=user_id).values('finance_id')]
unrecommend = list(set(unlike_finance_ids + already_mark_ids))
if finance_id and finance_id not in unrecommend:
unrecommend.append(finance_id)
finance_list = Finances.objects.filter(id__in=recommend_list).exclude(id__in=unrecommend).distinct().order_by(
"-like_num")
return finance_list
if __name__ == "__main__":
main()
# 加载模型进行预测
for i in range(1, 7):
print('************************', i, '**************************')
embedding_main(i)
输出:
准确率是0.538,精准度是0.751,召回率是0.538,F1值是0.603
5、总结
我们可以看到,整个流程,深度学习框架Tensorflow帮我们做了大部分的工作,我们其实只是简单的提供了基础数据而已。
首先定义一个embedding (多维空间) 用来理解需要学习的原始数据 :
一个用户对象(含一个属性userId)
一个图书对象(含三个属性:bookId, userId, rating (用户评分))
这里需要进行学习的具体就是让机器理解那个“评分:rating”的含义)这里定义的embedding 维度为64, 本质就是让机器把评分rating 的值当作成一个64维度的空间来进行理解(其实就是从这个rating值当中提取出64个特征来重新定义这个rating)
随后对embedding 进行降维处理:
具体的操作与使用的降维函数曲线有关,这里采用的是先降为32维再降为1维的两道操作方式,原来的代表rating 的embedding 空间从64维降低到了1维。而此时的输出output 对象就是机器对rating完播向量所做出来的“自己的理解”。
最后通过对学习完的输出项output 进行mask(遮罩)测试,通过变换不同的mask(遮罩)来测试结果是否与原始数据相近,或一致,从而来证实机器学习的效果,也就是上文提到的反向传播方式的逆运算。