共计 8429 个字符,预计需要花费 22 分钟才能阅读完成。
毋庸讳言,和传统架构 (BS 开发 /CS 开发) 相比,人工智能技术的确有肯定的根底门槛,它注定不是大众化,普适化的货色。但也不能否定,人工智能技术也具备像传统架构一样“套路化”的流程,也就是说,咱们大可不必本人手动构建基于神经网络的机器学习零碎,间接应用深度学习框架反而更加简略,深度学习能够帮忙咱们主动地从原始数据中提取特色,不须要手动抉择和提取特色。
之前咱们手动构建了一个小型的神经网络,解决了机器学习的分类问题,本次咱们利用深度学习框架 Tensorflow2.11 构建一套基于神经网络协同过滤模型 (NCF) 的视频举荐零碎,解决预测问题,实现一个真正能够落地的我的项目。
举荐零碎倒退历程
“小伙子,要光盘吗?新的到货了,内容相当精彩!”
大概 20 年前,在北京中关村的街头,一位抱着婴儿的中年大妈兴奋地拽着笔者的胳臂,载歌载舞地举荐着她的“产品”,大略这就是最原始的举荐零碎雏形了。
事实上,时至今日,仍然有相似产品应用这样的套路,不管三七二十一,弄个首页大 Banner,间接怼用户脸上,且不论用户感不感兴趣,有没有用户点击和转化,这种强买强卖式的举荐,着实不怎么令人欢快。
所以举荐零碎解决的痛点应该是用户的趣味需要,给用户举荐喜爱的内容,才是举荐零碎的外围。
于是乎,启发式举荐算法(Memory-based algorithms)就应运而生了。
启发式举荐算法易于实现,并且举荐后果的可解释性强。启发式举荐算法又能够分为两类:
基于用户的协同过滤(User-based collaborative filtering):次要思考的是用户和用户之间的类似度,只有找出类似用户喜爱的物品,并预测指标用户对对应物品的评分,就能够找到评分最高的若干个物品举荐给用户。举个例子,李老师和闫老师领有类似的电影爱好,当新电影上映后,李老师对其示意喜爱,那么就能将这部电影举荐给闫老师。
基于物品的协同过滤(Item-based collaborative filtering):次要思考的是物品和物品之间的类似度,只有找到了指标用户对某些物品的评分,那么就能够对类似度高的相似物品进行预测,将评分最高的若干个类似物品举荐给用户。举个例子,如果用户 A、B、C 给书籍 X,Y 的评分都是 5 分,当用户 D 想要买 Y 书籍的时候,零碎会为他举荐 X 书籍,因为基于用户 A、B、C 的评分,零碎会认为喜爱 Y 书籍的人在很大水平上会喜爱 X 书籍。
启发式协同过滤算法是一种联合了基于用户的协同过滤和基于我的项目的协同过滤的算法,它通过启发式规定来预测用户对物品的评分。
然而,启发式协同过滤算法也存在一些缺点:
难以解决冷启动问题:当一个用户或一个物品没有足够的评分数据时,启发式协同过滤算法无奈对其进行无效的预测,因为它须要依赖于已有的评分数据。
对数据稠密性敏感:如果数据集中存在大量的缺失值,启发式协同过滤算法的预测准确率会受到影响,因为它须要依赖于残缺的评分数据来进行预测。
算法的可解释性较差:启发式协同过滤算法的预测后果是通过启发式规定得出的,这些规定可能很难被解释和了解。
受限于启发式规定的品质:启发式协同过滤算法的预测准确率受到启发式规定的品质影响,如果启发式规定得不到无效的优化和更新,算法的性能可能会受到影响。
说白了,这种基于启发式的协同过滤算法,很容易陷入一个小范畴的窘境,就是如果某个用户特地喜爱体育的视频,那么这种零碎就会玩命地举荐体育视频,实际上这个人很有可能也喜爱艺术类的视频,然而囿于冷启动问题,无奈进行举荐。
为了解决下面的问题,基于神经网络的协同过滤算法诞生了,神经网络的协同过滤算法能够通过将用户和物品的特征向量作为输出,来预测用户对新物品的评分,从而解决冷启动问题。
对数据稠密性的鲁棒性:神经网络的协同过滤算法能够主动学习用户和物品的特征向量,并可能通过这些向量来预测评分,因而对于数据稠密的状况也能进行无效的预测。
更好的预测准确率:神经网络的协同过滤算法能够通过多层非线性变换来学习用户和物品之间的简单关系,从而可能进步预测准确率。
可解释性和灵活性:神经网络的协同过滤算法能够通过调整网络结构和参数来优化预测准确率,并且能够通过可视化办法来解释预测后果。
所以基于神经网络协同过滤模型是目前举荐零碎的支流状态。
基于稠密矩阵的视频完播数据
首先结构咱们的数据矩阵 test.csv 文件:
User,Video 1,Video 2,Video 3,Video 4,Video 5,Video 6
User1,10,3,,,,
User2,,10,,10,5,1
User3,,,9,,,
User4,6,1,,8,,9
User5,1,,1,,10,4
User6,1,4,1,,10,1
User7,,2,1,2,,8
User8,,,,1,,
User9,1,,10,,3,1
这里横轴是视频数据,纵轴是用户,对应的数据是用户对于视频的完播水平,10 代表看完了,1 则代表只看了百分之十,留空的代表没有看。
编写 ncf.py 脚本,将数据读入内存并输入:
import pandas as pd
# set pandas to show all columns without truncation and line breaks
pd.set_option('display.max_columns', 1000)
pd.set_option('display.width', 1000)
# data = np.loadtxt('data/test-data.csv', delimiter=',', dtype=int, skiprows=1,)
data = pd.read_csv('data/test-data.csv')
print(data)
程序返回:
User Video 1 Video 2 Video 3 Video 4 Video 5 Video 6
0 User1 10.0 3.0 NaN NaN NaN NaN
1 User2 NaN 10.0 NaN 10.0 5.0 1.0
2 User3 NaN NaN 9.0 NaN NaN NaN
3 User4 6.0 1.0 NaN 8.0 NaN 9.0
4 User5 1.0 NaN 1.0 NaN 10.0 4.0
5 User6 1.0 4.0 1.0 NaN 10.0 1.0
6 User7 NaN 2.0 1.0 2.0 NaN 8.0
7 User8 NaN NaN NaN 1.0 NaN NaN
8 User9 1.0 NaN 10.0 NaN 3.0 1.0
高深莫测。
有数据的列代表用户看过,1-10 代表看了之后的完播水平,如果没看过就是 NAN,当初咱们的目标就是“猜”进去这些没看过的视频的完播数据是多少?从而依据完播数据实现视频举荐零碎。
矩阵拆解算法
有一种举荐算法是基于矩阵拆解,通过假如的因素去“猜”稠密矩阵的空缺数据,猜出来之后,再通过反向流传的逆运算来反推稠密矩阵已存在的数据是否正确,从而判断“猜”进去的数据是否正确:
艰深地讲,跟算命差不多,然而基于数学原理,如果通过反推证实针对一个人的算命策略都是对的,那么就把这套流程利用到其他人身上。
然而这套逻辑过于线性,也就是因素过于繁多,比方我喜爱彩色的汽车,那么就会给我推所有彩色的货色,其实可能彩色的因素仅局限于汽车,是多重因素叠加导致的,所以矩阵拆解并不是一个十分好的解决方案。
基于神经网络
应用神经网络计算,必须将数据进行向量化操作:
# reset the column.index to be numeric
user_index = data[data.columns[0]]
book_index = data.columns
data = data.reset_index(drop=True)
data[data.columns[0]] = data.index.astype('int')
# print(data)
# print(data)
scaler = 10
# data = pd.DataFrame(data.to_numpy(), index=range(0,len(user_index)), columns=range(0,len(book_index)))
df_long = pd.melt(data, id_vars=[data.columns[0]],
ignore_index=True,
var_name='video_id',
value_name='rate').dropna()
df_long.columns = ['user_id', 'video_id', 'rating']
df_long['rating'] = df_long['rating'] / scaler
# replace the user_id to user by match user_index
df_long['user_id'] = df_long['user_id'].apply(lambda x: user_index[x])
# data = df_long.to_numpy()
print(df_long)
程序返回:
user_id video_id rating
0 User1 Video 1 1.0
3 User4 Video 1 0.6
4 User5 Video 1 0.1
5 User6 Video 1 0.1
8 User9 Video 1 0.1
9 User1 Video 2 0.3
10 User2 Video 2 1.0
12 User4 Video 2 0.1
14 User6 Video 2 0.4
15 User7 Video 2 0.2
20 User3 Video 3 0.9
22 User5 Video 3 0.1
23 User6 Video 3 0.1
24 User7 Video 3 0.1
26 User9 Video 3 1.0
28 User2 Video 4 1.0
30 User4 Video 4 0.8
33 User7 Video 4 0.2
34 User8 Video 4 0.1
37 User2 Video 5 0.5
40 User5 Video 5 1.0
41 User6 Video 5 1.0
44 User9 Video 5 0.3
46 User2 Video 6 0.1
48 User4 Video 6 0.9
49 User5 Video 6 0.4
50 User6 Video 6 0.1
51 User7 Video 6 0.8
53 User9 Video 6 0.1
这里 scaler=10 作为数据范畴的阈值,让计算机将完播数据散列成 0 - 1 之间的浮点数,便于神经网络进行计算。
随后装置 Tensorflow 框架:
pip3 install tensorflow
如果是 Mac 用户,请装置 mac 版本:
pip3 install tensorflow-macos
接着针对数据进行打标签操作:
import numpy as np
import pandas as pd
import tensorflow as tf
from sklearn.model_selection import train_test_split
from sklearn.preprocessing import LabelEncoder
# dataset = pd.read_csv(url, compression='zip', usecols=['userId', 'movieId', 'rating'])
dataset = df_long
# Encode the user and video IDs
user_encoder = LabelEncoder()
video_encoder = LabelEncoder()
dataset['user_id'] = user_encoder.fit_transform(dataset['user_id'])
dataset['video_id'] = video_encoder.fit_transform(dataset['video_id'])
# Split the dataset into train and test sets
# train, test = train_test_split(dataset, test_size=0.2, random_state=42)
train = dataset
# Model hyperparameters
num_users = len(dataset['user_id'].unique())
num_countries = len(dataset['video_id'].unique())
随后定义 64 个维度针对向量进行解决:
embedding_dim = 64
# Create the NCF model
inputs_user = tf.keras.layers.Input(shape=(1,))
inputs_video = tf.keras.layers.Input(shape=(1,))
embedding_user = tf.keras.layers.Embedding(num_users, embedding_dim)(inputs_user)
embedding_video = tf.keras.layers.Embedding(num_countries, embedding_dim)(inputs_video)
# Merge the embeddings using concatenation, you can also try other merging methods like dot product or multiplication
merged = tf.keras.layers.Concatenate()([embedding_user, embedding_video])
merged = tf.keras.layers.Flatten()(merged)
# Add fully connected layers
dense = tf.keras.layers.Dense(64, activation='relu')(merged)
dense = tf.keras.layers.Dense(32, activation='relu')(dense)
output = tf.keras.layers.Dense(1, activation='sigmoid')(dense)
# Compile the model
model = tf.keras.Model(inputs=[inputs_user, inputs_video], outputs=output)
model.compile(optimizer='adam', loss='mse', metrics=['mae'])
这里定义了一个 64 维度的 embedding 类用来对向量进行解决。相当于就是把属于数据当中的所有特色都设定成一个能够用一个 64 维向量标识的货色,而后通过降维解决之后使得机器能以一个低维的数据流形来“了解”高维的原始数据的形式来“了解”数据的“含意”,
从而实现机器学习的目标。而为了测验机器学习的成绩(即机器是否有真正了解特色的含意),则应用 mask(遮罩)的形式,将原始数据当中的一部分无关外围的内容“遮掉”,而后再尝试进行输入输出操作,如果输入输出操作的后果与没有遮罩的后果进行比拟后足够相近,或者完全相同,则断定机器有胜利学习了解到向量的含意。
这里须要留神的是,因为 embedding 这个词其实是有肯定水平的误用的关系,所以不要尝试用原来的语义去了解这个词,艰深地讲,能够把它了解为“特色(feature)”,即从原始数据中提取进去的一系列的特色属性,至于具体是什么特色,不重要。
这里有 64 个维度,那就能够认为是从输出的原始数据当中提取 64 个“特色”,而后用这个特色模型去套用所有的输出的原始数据,而后再将这些数据通过降维转换,最终把每一个输出的向量转换成一个 1 维的非凡字符串,而后让机器实现“了解简单的输出”的目标,而那个所谓的训练过程,其实也就是一直地用遮罩 mask 去遮掉非核心的数据,而后比照输入后果,来看机器是否胜利实现了学习的目标。
说白了,和矩阵拆解差不多,只不过矩阵拆解是线性单维度,而神经网络是非线性多维度。
最初进行训练和输入:
model.fit([train['user_id'].values, train['video_id'].values],
train['rating'].values,
batch_size=64,
epochs=100,
verbose=0,
# validation_split=0.1,
)
result_df = {}
for user_i in range(1, 10):
user = f'User{user_i}'
result_df[user] = {}
for video_i in range(1, 7):
video = f'Video {video_i}'
pred_user_id = user_encoder.transform([user])
pred_video_id = video_encoder.transform(视频警告:播放链接不能为空)
result = model.predict(x=[pred_user_id, pred_video_id], verbose=0)
result_df[user]视频警告:播放链接不能为空 = result[0][0]
result_df = pd.DataFrame(result_df).T
result_df *= scaler
print(result_df)
程序返回:
Video 1 Video 2 Video 3 Video 4 Video 5 Video 6
User1 9.143433 3.122697 5.831852 8.930688 9.223139 9.148163
User2 2.379406 9.317654 9.280337 9.586231 5.115635 0.710877
User3 6.046935 8.950342 9.335093 9.546472 8.487216 5.069511
User4 6.202362 1.341177 2.609368 7.755390 9.160558 8.974072
User5 1.134012 1.772043 0.634183 3.741076 9.297663 3.924277
User6 0.488006 4.060344 1.116192 4.625140 9.264144 1.199519
User7 2.820735 0.898690 0.560579 2.215827 8.604731 7.889819
User8 0.244587 1.062029 0.360087 1.069786 7.698551 1.286932
User9 1.337930 8.537857 9.329366 9.123328 3.074733 0.774436
咱们能够看到,机器通过神经网络的“学习”,间接“猜出来”所有用户未播放视频的完播水平。那么,咱们只须要给这些用户举荐他未看的,然而机器“猜”他完播高的视频即可。
总结
咱们能够看到,整个流程简略的令人发指,深度学习框架 Tensorflow 帮咱们做了大部分的工作,咱们其实只是简略的提供了根底数据而已。
首先定义一个 embedding (多维空间) 用来了解须要学习的原始数据:
一个用户对象(含一个属性 userId)
一个视频对象(含三个属性:videoId, userId, rating (完播向量))
这里须要进行学习的具体就是让机器了解那个“完播向量:rating”的含意)这里定义的 embedding 维度为 64, 实质就是让机器把完播向量 rating 的值当作成一个 64 维度的空间来进行了解(其实就是从这个 rating 值当中提取出 64 个特色来从新定义这个 rating)
随后对 embedding 进行降维解决:
具体的操作与应用的降维函数曲线无关,这里采纳的是先降为 32 维再降为 1 维的两道操作形式,原来的代表 rating 的 embedding 空间从 64 维升高到了 1 维。而此时的输入 output 对象就是机器对 rating 完播向量所做进去的“本人的了解”。
最初通过对学习完的输入项 output 进行 mask(遮罩)测试,通过变换不同的 mask(遮罩)来测试后果是否与原始数据相近,或统一,从而来证实机器学习的成果,也就是上文提到的反向传播方式的逆运算。
结语
可能仍然有敌人对这套零碎的底层不太理解,那么,如果咱们用“白话文”的模式进行解释:比方有一幅油画,油画相比完播量,必定是多维度的,因为画外面有色彩、格调、解析度、对比度、饱和度等等特征参数,此时咱们让机器先看残缺的这幅画,而后用机器学习的形式让它学习(即 embedding 形式),接着把这幅画遮掉一部分与主题无关的局部,而后再测试机器让它用学习到的数据(即 embedding 实现降维解决之后的数据)去尝试还原整幅画,随后比照还原的整幅画和原始油画有多大差异,如果差异没有或者很小,则证实机器学习胜利了,机器的确学会了这副画,而后就让机器依照这套逻辑去画相似的画,最初把这一“类”的画举荐给没有鉴赏过的用户,从而实现举荐零碎,就这么简略。
最初,奉上视频举荐零碎我的项目代码,与众乡亲同飨:github.com/zcxey2911/NeuralCollaborativeFiltering\_NCF\_Tensorflow