python实现基于SVD矩阵分解的电影推荐系统设计

       大家好,我是带我去滑雪!

       SVD 是一种矩阵分解技术,通过将空间维数从 N 维降到 K 维(其中K<N)来减少数据集的特征数。在推荐系统中,SVD 被用作一种协同过滤技术。使用矩阵结构,其中行表示用户,列表示电影。这个矩阵的对应位置数值是用户给项目的评分。该矩阵的分解是通过奇异值分解完成的。从高级(用户项评级)矩阵的因子分解中找到矩阵的子矩阵。奇异值分解是将一个矩阵 A 分解为 U, S,V 三个其他矩阵的方法。

       本期利用抓取IMDB的英文网站上的电影相关数据,实现基于SVD矩阵分解的电影推荐系统设计。

目录

1、抓取IMDB网站上电影相关数据

(1)爬取的步骤

(2)代码

(3)部分数据展示

 2、基于SVD矩阵分解的电影推荐系统设计

(1)导入相关模块与数据

(2)构建用户与电影的评分矩阵

(3)实现矩阵分解,求奇异值

(4)SVD评分估计

(5)使用SVD模型为用户推荐电影


1、抓取IMDB网站上电影相关数据

        抓取的是一个叫IMDB的英文网站,因为国内没有找到那种有用户分别对电影打分的,比如豆瓣、腾讯等等。介绍一下什么是IMDB:IMDB,全称为Internet Movie Database,中文意为“互联网电影数据库”,是世界上最大的、最具权威性的电影、电视剧和演员等相关信息的在线数据库之一,也是全球电影和电视节目工作者与业内人士交流、沟通和分享资源的重要平台,同时也是广大电影爱好者、编剧和导演等获取电影、电视剧资料和资源的重要来源之一。

       IMDB数据库由Col Needham于1990年创办,在1996年被Amazon.com公司收购。这个在线电影数据库包括了全球 绝大多数电影、电视剧、电视综艺以及演员、制片人、导演等电影从业人员的信息资料,包括电影/电视剧的剧情、演职员表、评分、票房、影评等信

(1)爬取的步骤

步骤1:获取IMDB的数据源URL

       对于IMDB的电影信息,可以通过IMDB提供的API进行调用,也可以通过获取IMDB的数据源URL来进行爬取。获取IMDB的数据源URL的方法有很多,最简单的方法是在IMDB网站上手动搜索你想要爬取的电影信息,然后将搜索结果页URL中的信息复制下来。

步骤2:爬取IMDB电影信息页面

       获取了IMDB电影的数据源URL,可以使用爬虫程序爬取电影信息页面了,可以使用Python编程语言中的一个叫做“Requests”的库来进行页面请求和数据获取。爬虫程序需要发送GET请求包含电影ID的URL,并用BeautifulSoup等Web解析器来解析该电影页面源代码,以获取电影信息。可获取的信息包括电影、主演、上映年份、电影链接、电影类型、用户ID以及电影评分等信息。

步骤3:数据存储

       完成电影信息的爬取,可以将这些信息存储在本地计算机的数据库、Excel文件或其他文本文件中,以备之后的分析和使用。

步骤4:保证爬虫正常进行

       在爬取IMDB电影信息时,需要注意到IMDB会不时地更新网站的结构和数据,需要根据页面结构和网站API的更新来进行相应的调整,以保证成功爬取数据。此外,需要保证爬虫程序的请求限制和频率合理,以防止影响IMDB网站和本地计算机的性能。

(2)代码

import requests  # 发送请求

from bs4 import BeautifulSoup  # 解析网页

import pandas as pd  # 存取csv

from time import sleep  # 等待时间

movie_name = [] 

movie_url = [] 

movie_star = []

movie_star_people = [] 

movie_director = [] 

movie_actor = [] 

movie_year = [] 

movie_country = [] 

movie_type = [] 

def get_movie_info(url, headers):

       res = requests.get(url, headers=headers)

       soup = BeautifulSoup(res.text, 'html.parser')

       for movie in soup.select('.item'):

              name = movie.select('.hd a')[0].text.replace('\n', '')             movie_name.append(name)

              url = movie.select('.hd a')[0]['href'] 

              movie_url.append(url)

              star = movie.select('.rating_num')[0].text 

              movie_star.append(star)

              star_people = movie.select('.star span')[3].text 

              star_people = star_people.strip().replace('', '')

              movie_star_people.append(star_people)

              movie_infos = movie.select('.bd p')[0].text.strip() 

              director = movie_infos.split('\n')[0].split('   ')[0]

              movie_director.append(director)

              try: 

                     actor = movie_infos.split('\n')[0].split('   ')[1]

                     movie_actor.append(actor)

              except: 

                     movie_actor.append(None)

              if name == '  /  The Monkey King': 

                     year0 = movie_infos.split('\n')[1].split('/')[0].strip()

                     year1 = movie_infos.split('\n')[1].split('/')[1].strip()

                     year2 = movie_infos.split('\n')[1].split('/')[2].strip()

                     year = year0 + '/' + year1 + '/' + year2

                     movie_year.append(year)

                     country = movie_infos.split('\n')[1].split('/')[3].strip()

                     movie_country.append(country)

                     type = movie_infos.split('\n')[1].split('/')[4].strip()

                     movie_type.append(type)

              else: 

                     year = movie_infos.split('\n')[1].split('/')[0].strip()

                     movie_year.append(year)

                     country = movie_infos.split('\n')[1].split('/')[1].strip()

                     movie_country.append(country)

                     type = movie_infos.split('\n')[1].split('/')[2].strip()

                     movie_type.append(type)

def save_to_csv(csv_name):

       """

       数据保存到csv

       :return: None

       """

       df = pd.DataFrame()  # 初始化一个DataFrame对象

       df['电影名称'] = movieId

       df['电影评分'] = ratings

       df['电影链接'] = 电影链接

       df['主演'] = movie_actor

       df['上映年份'] = movie_year

       df['用户名称 '] =userId

       df['电影类型'] =电影类型

       df.to_csv(csv_name, encoding='utf_8_sig')  # 分别将将数据保存到csv文件

if __name__ == "__main__":

       # 定义一个请求头(防止反爬)

       headers = {

              'User-Agent': 'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/68.0.3440.106 Safari/537.36'}

      

       for i in range(390):  # 爬取共390页,每页25条数据

              page_url = 'https://www.imdb.com/start={}'.format(str(i *390))

              print('开始爬取第{}页,地址是:{}'.format(str(i + 1), page_url))

              get_movie_info(page_url, headers)

              sleep(1)  # 等待1秒(防止反爬)        

(3)部分数据展示

656068ef166c495db119a4a25495d3fb.png

87c965dfb808414dbadca466f73df984.png

 2、基于SVD矩阵分解的电影推荐系统设计

(1)导入相关模块与数据

import numpy as np

import pandas as pd

data = pd.read_csv('data.txt', sep='\t', header=None)

data.drop(3, inplace=True, axis=1) # 去掉时间戳

data.columns = ['user_id', 'movie_id', 'rating']

data

输出结果:

27b636b4fe20410f94c309bc7bf22623.png

movie_data= pd.read_csv('movie_data.txt', sep='\t',encoding="UTF-16LE")

movie_data

输出结果:

93e9a588529f4a719b701fc8d146c75f.png

(2)构建用户与电影的评分矩阵

       创建行为电影 id,列为用户 id 的矩阵,每一个 x行和列交叉的位置是用户对电影评分,这是一个稀疏矩阵,其中很多 0 位置表示用户没有给该电影打过分数 。

ratings_mat = np.ndarray(shape=(np.max(data.movie_id.values),np.max(data.user_id.values)),dtype=np.uint8)

ratings_mat[data.movie_id.values-1,data.user_id.values-1] = data.rating.values

pd.DataFrame(ratings_mat)

输出结果:

e30d83ecf860418b8cb22cf33ad9bc8d.png

(3)实现矩阵分解,求奇异值

      计算A:

normalised_mat = ratings_mat - np.asarray([(np.mean(ratings_mat, 1))]).T

normalised_mat

A = normalised_mat.T/np.sqrt(ratings_mat.shape[0]-1)

A

输出结果:

75d3b9f6b1d04dcea0383433f0423c70.png

      计算U、V、eq?%5Csum_%7B%7D%5E%7B%7D的值:

U,S,V= np.linalg.svd(A)

U,S,V

(4)SVD评分估计

def top_cosine_similarity(data,movie_id,top_n=10):

    index = movie_id - 1

    movie_row = data[index,:]

    magnitude = np.sqrt(np.einsum('ij, ij -> i', data, data))

    similarity = np.dot(movie_row, data.T) / (magnitude[index] * magnitude)

    sort_indexes = np.argsort(-similarity)

return sort_indexes[:top_n]

def print_similar_movies(movie_data, movie_id, top_indexes):

    print('Recommendations for {0}: \n'.format(

    movie_data[movie_data.movie_id == movie_id].title.values[0]))

    for id in top_indexes + 1:

        print(movie_data[movie_data.movie_id == id].title.values[0])

import numpy as np

import pandas as pd

# 用DataFrame来储存数据,格式为userid, itemid, rating

df = pd.read_csv('data.txt', sep='\t', header=None)

df.drop(3, inplace=True, axis=1) # 去掉时间戳

df.columns = ['uid', 'iid', 'rating']

# 随机打乱划分训练和测试集

df = df.sample(frac=1, random_state=0)

train_set = df.iloc[:int(len(df)*0.75)]

test_set = df.iloc[int(len(df)*0.75):]

n_users = max(df.uid)+1

n_items = max(df.iid)+1

class SVD(object):

    def __init__(self, n_epochs, n_users, n_items, n_factors, lr, reg_rate, random_seed=0):

        self.n_epochs = n_epochs

        self.lr = lr

        self.reg_rate = reg_rate

        np.random.seed(random_seed)

        self.pu = np.random.randn(n_users, n_factors) / np.sqrt(n_factors) # 参数初始化不能太大

        self.qi = np.random.randn(n_items, n_factors) / np.sqrt(n_factors)

       

    def predict(self, u, i):

        return np.dot(self.qi[i], self.pu[u])

       

    def fit(self, train_set, verbose=True):

        for epoch in range(self.n_epochs):

            mse = 0

            for index, row in train_set.iterrows():

                u, i, r = row.uid, row.iid, row.rating

                error = r - self.predict(u, i)

                mse += error**2

                tmp = self.pu[u]

                self.pu[u] += self.lr * (error * self.qi[i] - self.reg_rate * self.pu[u])

                self.qi[i] += self.lr * (error * tmp - self.reg_rate * self.qi[i])

            if verbose == True:

                rmse = np.sqrt(mse / len(train_set))

                print('epoch: %d, rmse: %.4f' % (epoch, rmse))

        return self

   

    def test(self, test_set):

        predictions = test_set.apply(lambda x: self.predict(x.uid, x.iid), axis=1)

        rmse = np.sqrt(np.sum((test_set.rating - predictions)**2) / len(test_set))

        return rmse

   

svd = SVD(n_epochs=20, n_users=n_users, n_items=n_items, n_factors=35, lr=0.005, reg_rate=0.02)

svd.fit(train_set, verbose=True)

svd.test(test_set)

输出结果:

0.932

funk_svd.predict(299, 282)

#测试集中的某一条数据,真实评分为4,预测为3.3946690868636873

输出结果:

epoch: 0, rmse: 3.6946

epoch: 1, rmse: 3.0856

epoch: 2, rmse: 1.8025

epoch: 3, rmse: 1.3196

epoch: 4, rmse: 1.1282

epoch: 5, rmse: 1.0339

epoch: 6, rmse: 0.9791

epoch: 7, rmse: 0.9426

epoch: 8, rmse: 0.9155

epoch: 9, rmse: 0.8936

epoch: 10, rmse: 0.8748

epoch: 11, rmse: 0.8579

epoch: 12, rmse: 0.8424

epoch: 13, rmse: 0.8279

epoch: 14, rmse: 0.8140

epoch: 15, rmse: 0.8008

epoch: 16, rmse: 0.7881

epoch: 17, rmse: 0.7758

epoch: 18, rmse: 0.7640

epoch: 19, rmse: 0.7524

3.3946690868636873

        解读:经过20次迭代后,测试集中的(用户id为299,电影id为282)一条数据,真实评分为4,预测为3.3946690868636873,模型的得分达到0.932。

(5)使用SVD模型为用户推荐电影

 k = 2

movie_id =15

top_n =5

sliced = V.T[:, :k]

indexes = top_cosine_similarity(sliced, movie_id, top_n)

print_similar_movies(movie_data, movie_id, indexes)

输出结果:

Recommendations for Cutthroat Island (1995):

Cutthroat Island (1995)

Strictly Ballroom (1992)

Fish Called Wanda, A (1988)

2 Days in the Valley (1996)

Right Stuff, The (1983)

       解读:设置参考k为2,使用SVD模型计算出该用户对所有电影的评分,按照预测分值大小,将排名前5的电影推荐给用户。通过输出结果可以看见,SVD模型给我们推荐的5部电影分别为Cutthroat Island (1995)、Strictly Ballroom (1992)、Fish Called Wanda, A (1988)、2 Days in the Valley (1996)、Right Stuff, The (1983)。


 需要数据集的家人们可以去百度网盘(永久有效)获取:

链接:https://pan.baidu.com/s/1E59qYZuGhwlrx6gn4JJZTg?pwd=2138
提取码:2138 


更多优质内容持续发布中,请移步主页查看,若有问题可私信博主!

若有问题可邮箱联系:1736732074@qq.com 

博主的WeChat:TCB1736732074

   点赞+关注,下次不迷路!

  • 7
    点赞
  • 31
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

(备考中,暂停更新)4.14 于武汉

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值