├── README.md
├── 微信客户端
├── app.js
├── app.json
├── app.wxss
├── pages
│ ├── index
│ │ ├── index.js
│ │ ├── index.json
│ │ ├── index.wxml
│ │ └── index.wxss
│ ├── random_configuration
│ │ ├── random_configuration.js
│ │ ├── random_configuration.json
│ │ ├── random_configuration.wxml
│ │ └── random_configuration.wxss
│ ├── recommedation
│ │ ├── recommedation.js
│ │ ├── recommedation.json
│ │ ├── recommedation.wxml
│ │ └── recommedation.wxss
│ ├── recommedation_configuration
│ │ ├── recommedation_configuration.js
│ │ ├── recommedation_configuration.json
│ │ ├── recommedation_configuration.wxml
│ │ └── recommedation_configuration.wxss
│ ├── select
│ │ ├── select.js
│ │ ├── select.json
│ │ ├── select.wxml
│ │ └── select.wxss
│ └── upload
│ │ ├── upload.js
│ │ ├── upload.json
│ │ ├── upload.wxml
│ │ └── upload.wxss
├── project.config.json
└── utils
│ └── util.js
├── 服务端
├── .DS_Store
├── .idea
│ ├── misc.xml
│ ├── modules.xml
│ ├── workspace.xml
│ └── 推荐系统.iml
├── Music_Recommendation.csv
├── Music_Recommendation_Random.csv
├── Readme.md
├── __pycache__
│ ├── apriori.cpython-36.pyc
│ ├── config.cpython-36.pyc
│ ├── recommendation.cpython-36.pyc
│ └── utils.cpython-36.pyc
├── apriori.py
├── client.py
├── config.py
├── recommendation.py
├── server.py
├── uploads
│ └── Music_Recommendation.csv
└── utils.py
├── 演示视屏.mp4
└── 项目结构图.png
/README.md:
--------------------------------------------------------------------------------
1 | # 项目名称
2 | 基于Apriori算法的音乐推荐系统
3 |
4 | # 项目简介
5 | 该系统由服务端和微信客户端两部分组成,服务端基于Apriori算法。
6 | 训练关联规则模型(Apriori)以查找过去一段时间用户所听音乐类
7 | 型的最相关项.该算法将大多数用户的产品偏好关联起来,并可用于生
8 | 成音乐类型推荐。
9 |
10 | # 项目结构图
11 | 
12 |
13 | # 项目部署
14 | 1、修改服务端config.py文件
15 | 2、运行服务端server.py文件
16 | 3、修改微信客户端服务器域名配置
17 | 4、运行微信客户端
18 |
19 | # 项目演示视屏
20 | https://github.com/JunhaoCheng/Recommendation-System/blob/master/演示视屏.mp4
21 |
--------------------------------------------------------------------------------
/微信客户端/app.js:
--------------------------------------------------------------------------------
1 | //app.js
2 | App({
3 | onLaunch: function () {
4 | // 展示本地存储能力
5 | var logs = wx.getStorageSync('logs') || []
6 | logs.unshift(Date.now())
7 | wx.setStorageSync('logs', logs)
8 |
9 | // 登录
10 | wx.login({
11 | success: res => {
12 | // 发送 res.code 到后台换取 openId, sessionKey, unionId
13 | }
14 | })
15 | // 获取用户信息
16 | wx.getSetting({
17 | success: res => {
18 | if (res.authSetting['scope.userInfo']) {
19 | // 已经授权,可以直接调用 getUserInfo 获取头像昵称,不会弹框
20 | wx.getUserInfo({
21 | success: res => {
22 | // 可以将 res 发送给后台解码出 unionId
23 | this.globalData.userInfo = res.userInfo
24 |
25 | // 由于 getUserInfo 是网络请求,可能会在 Page.onLoad 之后才返回
26 | // 所以此处加入 callback 以防止这种情况
27 | if (this.userInfoReadyCallback) {
28 | this.userInfoReadyCallback(res)
29 | }
30 | }
31 | })
32 | }
33 | }
34 | })
35 | },
36 | globalData: {
37 | userInfo: null,
38 | recommend_configuration: {},
39 | random_configuration: {},
40 | result: {}
41 | }
42 | })
--------------------------------------------------------------------------------
/微信客户端/app.json:
--------------------------------------------------------------------------------
1 | {
2 | "pages":[
3 | "pages/index/index",
4 | "pages/recommedation_configuration/recommedation_configuration",
5 | "pages/select/select",
6 | "pages/upload/upload",
7 | "pages/random_configuration/random_configuration",
8 | "pages/recommedation/recommedation"
9 | ],
10 | "window":{
11 | "backgroundTextStyle":"light",
12 | "navigationBarBackgroundColor": "#00BFFF",
13 | "navigationBarTitleText": "音乐类型推荐系统",
14 | "navigationBarTextStyle":"white"
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/微信客户端/app.wxss:
--------------------------------------------------------------------------------
1 | /**app.wxss**/
2 | .container {
3 | height: 100%;
4 | display: flex;
5 | flex-direction: column;
6 | align-items: center;
7 | justify-content: space-between;
8 | padding: 200rpx 0;
9 | box-sizing: border-box;
10 | }
11 |
--------------------------------------------------------------------------------
/微信客户端/pages/index/index.js:
--------------------------------------------------------------------------------
1 | //index.js
2 | //获取应用实例
3 | const app = getApp()
4 |
5 | Page({
6 | data: {
7 | motto: 'Hello World',
8 | userInfo: {},
9 | hasUserInfo: false,
10 | canIUse: wx.canIUse('button.open-type.getUserInfo')
11 | },
12 | //事件处理函数
13 | bindViewTap: function() {
14 | wx.navigateTo({
15 | url: '../recommedation_configuration/recommedation_configuration'
16 | })
17 | },
18 | onLoad: function () {
19 | if (app.globalData.userInfo) {
20 | this.setData({
21 | userInfo: app.globalData.userInfo,
22 | hasUserInfo: true
23 | })
24 | } else if (this.data.canIUse){
25 | // 由于 getUserInfo 是网络请求,可能会在 Page.onLoad 之后才返回
26 | // 所以此处加入 callback 以防止这种情况
27 | app.userInfoReadyCallback = res => {
28 | this.setData({
29 | userInfo: res.userInfo,
30 | hasUserInfo: true
31 | })
32 | }
33 | } else {
34 | // 在没有 open-type=getUserInfo 版本的兼容处理
35 | wx.getUserInfo({
36 | success: res => {
37 | app.globalData.userInfo = res.userInfo
38 | this.setData({
39 | userInfo: res.userInfo,
40 | hasUserInfo: true
41 | })
42 | }
43 | })
44 | }
45 | },
46 | getUserInfo: function(e) {
47 | console.log(e)
48 | app.globalData.userInfo = e.detail.userInfo
49 | this.setData({
50 | userInfo: e.detail.userInfo,
51 | hasUserInfo: true
52 | })
53 | }
54 | })
55 |
--------------------------------------------------------------------------------
/微信客户端/pages/index/index.json:
--------------------------------------------------------------------------------
1 | {
2 | "usingComponents": {}
3 | }
--------------------------------------------------------------------------------
/微信客户端/pages/index/index.wxml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | {{userInfo.nickName}}
8 |
9 |
10 |
11 | {{motto}}
12 |
13 |
14 |
--------------------------------------------------------------------------------
/微信客户端/pages/index/index.wxss:
--------------------------------------------------------------------------------
1 | /**index.wxss**/
2 | page {
3 | background: #00BFFF;
4 | }
5 |
6 | .userinfo {
7 | display: flex;
8 | flex-direction: column;
9 | align-items: center;
10 | }
11 |
12 | .userinfo-avatar {
13 | width: 128rpx;
14 | height: 128rpx;
15 | margin: 20rpx;
16 | border-radius: 50%;
17 | }
18 |
19 | .userinfo-nickname {
20 | color: #aaa;
21 | }
22 |
23 | .usermotto {
24 | margin-top: 200px;
25 | color: #FAFFF0
26 | }
27 |
28 | .button {
29 | width: 360rpx;
30 | height: 90rpx;
31 | margin: 40rpx;
32 | background-color: rgb(252, 126, 67);
33 | color: white;
34 | border-radius: 98rpx;
35 | background: bg_red;
36 | }
--------------------------------------------------------------------------------
/微信客户端/pages/random_configuration/random_configuration.js:
--------------------------------------------------------------------------------
1 | // pages/random_configuration/random_configuration.js
2 | const app = getApp()
3 | Page({
4 |
5 | /**
6 | * 页面的初始数据
7 | */
8 | data: {
9 |
10 | },
11 |
12 | /**
13 | * 生命周期函数--监听页面加载
14 | */
15 | onLoad: function (options) {
16 |
17 | },
18 |
19 | /**
20 | *确认按钮函数,保存数据,
21 | *跳转界面
22 | */
23 | confirm: function (e) {
24 | //保存数据
25 | var val = e.detail.value
26 | console.log(val)
27 | app.globalData.random_configuration = val
28 |
29 | //获取随机生成文件参数配置
30 | var random_configuration = app.globalData.random_configuration
31 | //获取推荐参数配置
32 | var recommend_configuration = app.globalData.recommend_configuration
33 | //参数配置
34 | var configuration = {}
35 | for (var k in random_configuration) {
36 | configuration[k] = random_configuration[k]
37 | }
38 | for (var k in recommend_configuration) {
39 | configuration[k] = recommend_configuration[k]
40 | }
41 | console.log(configuration)
42 |
43 | wx.showLoading({
44 | title: '处理中,请耐心等待',
45 | mask: true
46 | })
47 | //请求接口处理并以json格式返回结果
48 | wx.request({
49 | url: 'https://www.ponma.cn:5000/random', // 接口地址
50 | data: configuration,
51 | header: {
52 | 'content-type': 'application/json' // 默认值
53 | },
54 | success(res) {
55 | console.log(res.data)
56 | //保存结果
57 | app.globalData.result = res.data
58 | wx.hideLoading()
59 |
60 | //界面跳转
61 | wx.navigateTo({
62 | url: '/pages/recommedation/recommedation'
63 | })
64 | },
65 | fail(err) {
66 | wx.hideLoading()
67 | wx.showToast({
68 | title: '处理失败',
69 | icon: 'none',
70 | duration: 2000
71 | })
72 | }
73 | })
74 | },
75 |
76 | /**
77 | * 生命周期函数--监听页面初次渲染完成
78 | */
79 | onReady: function () {
80 |
81 | },
82 |
83 | /**
84 | * 生命周期函数--监听页面显示
85 | */
86 | onShow: function () {
87 |
88 | },
89 |
90 | /**
91 | * 生命周期函数--监听页面隐藏
92 | */
93 | onHide: function () {
94 |
95 | },
96 |
97 | /**
98 | * 生命周期函数--监听页面卸载
99 | */
100 | onUnload: function () {
101 |
102 | },
103 |
104 | /**
105 | * 页面相关事件处理函数--监听用户下拉动作
106 | */
107 | onPullDownRefresh: function () {
108 |
109 | },
110 |
111 | /**
112 | * 页面上拉触底事件的处理函数
113 | */
114 | onReachBottom: function () {
115 |
116 | },
117 |
118 | /**
119 | * 用户点击右上角分享
120 | */
121 | onShareAppMessage: function () {
122 |
123 | }
124 | })
--------------------------------------------------------------------------------
/微信客户端/pages/random_configuration/random_configuration.json:
--------------------------------------------------------------------------------
1 | {
2 | "usingComponents": {}
3 | }
--------------------------------------------------------------------------------
/微信客户端/pages/random_configuration/random_configuration.wxml:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/微信客户端/pages/random_configuration/random_configuration.wxss:
--------------------------------------------------------------------------------
1 | /* pages/random_configuration/random_configuration.wxss */
2 | page {
3 | background: #00BFFF;
4 | height: 100%;
5 | }
6 |
7 | .button {
8 | width: 250rpx;
9 | height: 90rpx;
10 | marigin:0rpx auto;
11 | background-color: rgb(252, 126, 67);
12 | color: white;
13 | border-radius: 98rpx;
14 | background: bg_red;
15 | }
16 |
17 | .section{
18 | margin-top: 5px;
19 | margin-left: 20px;
20 | margin-right: 20px;
21 | height: 35px;
22 | border: 2px solid red;
23 | border-radius: 25px;
24 | }
25 | .input{
26 | padding-left: 10px;
27 | height: 30px;
28 | }
29 |
30 | .title {
31 | margin-bottom: 20px;
32 | display: flex;
33 | align-items: center;
34 | justify-content: center;
35 | }
36 |
37 | .name {
38 | color: #FAFFF0;
39 | font-size: 50rpx;
40 | }
41 |
42 | .view-Name {
43 | color: #FFDEAD;
44 | font-size: 40rpx;
45 | display: flex;
46 | align-items: center;
47 | justify-content: center;
48 | }
49 |
50 | .view {
51 | margin: 40rpx;
52 | }
53 |
--------------------------------------------------------------------------------
/微信客户端/pages/recommedation/recommedation.js:
--------------------------------------------------------------------------------
1 | // pages/recommedation/recommedation.js
2 | const app = getApp()
3 | Page({
4 |
5 | /**
6 | * 页面的初始数据
7 | */
8 | data: {
9 | result: app.globalData.result
10 | },
11 |
12 | /**
13 | * 生命周期函数--监听页面加载
14 | */
15 | onLoad: function (options) {
16 | //把结果转为文本形式
17 | var results = app.globalData.result
18 | var res = ''
19 | for (var k in results) {
20 | res += k + '\n' + results[k] + '\n\n'
21 | }
22 | this.setData({result : res})
23 | },
24 |
25 | /**
26 | * 生命周期函数--监听页面初次渲染完成
27 | */
28 | onReady: function () {
29 |
30 | },
31 |
32 | /**
33 | * 生命周期函数--监听页面显示
34 | */
35 | onShow: function () {
36 |
37 | },
38 |
39 | /**
40 | * 生命周期函数--监听页面隐藏
41 | */
42 | onHide: function () {
43 |
44 | },
45 |
46 | /**
47 | * 生命周期函数--监听页面卸载
48 | */
49 | onUnload: function () {
50 |
51 | },
52 |
53 | /**
54 | * 页面相关事件处理函数--监听用户下拉动作
55 | */
56 | onPullDownRefresh: function () {
57 |
58 | },
59 |
60 | /**
61 | * 页面上拉触底事件的处理函数
62 | */
63 | onReachBottom: function () {
64 |
65 | },
66 |
67 | /**
68 | * 用户点击右上角分享
69 | */
70 | onShareAppMessage: function () {
71 |
72 | }
73 | })
--------------------------------------------------------------------------------
/微信客户端/pages/recommedation/recommedation.json:
--------------------------------------------------------------------------------
1 | {
2 | "usingComponents": {}
3 | }
--------------------------------------------------------------------------------
/微信客户端/pages/recommedation/recommedation.wxml:
--------------------------------------------------------------------------------
1 |
2 |
3 | 推荐结果
4 |
5 | {{result}}
--------------------------------------------------------------------------------
/微信客户端/pages/recommedation/recommedation.wxss:
--------------------------------------------------------------------------------
1 | /* pages/recommedation/recommedation.wxss */
2 | page {
3 | background: #00BFFF;
4 | height: 100%;
5 | }
6 |
7 | .title {
8 | margin-bottom: 20rpx;
9 | display: flex;
10 | align-items: center;
11 | justify-content: center;
12 | }
13 |
14 | .name {
15 | color: #FAFFF0;
16 | font-size: 50rpx;
17 | }
18 |
19 | .view-Name {
20 | color: #FFDEAD;
21 | font-size: 40rpx;
22 | display: flex;
23 | align-items: center;
24 | justify-content: center;
25 | }
--------------------------------------------------------------------------------
/微信客户端/pages/recommedation_configuration/recommedation_configuration.js:
--------------------------------------------------------------------------------
1 | // pages/recommedation_configuration/recommedation_configuration.js
2 | const app = getApp()
3 | Page({
4 |
5 | /**
6 | * 页面的初始数据
7 | */
8 | data: {
9 |
10 | },
11 |
12 | /**
13 | * 生命周期函数--监听页面加载
14 | */
15 | onLoad: function (options) {
16 |
17 | },
18 |
19 | /**
20 | *确认按钮函数,保存数据,
21 | *跳转界面
22 | */
23 | confirm: function (e) {
24 | //保存数据
25 | var val = e.detail.value
26 | console.log(val)
27 | app.globalData.recommend_configuration = val
28 |
29 | //界面跳转
30 | wx.navigateTo({
31 | url: '/pages/select/select'
32 | })
33 | },
34 |
35 | /**
36 | * 生命周期函数--监听页面初次渲染完成
37 | */
38 | onReady: function () {
39 |
40 | },
41 |
42 | /**
43 | * 生命周期函数--监听页面显示
44 | */
45 | onShow: function () {
46 |
47 | },
48 |
49 | /**
50 | * 生命周期函数--监听页面隐藏
51 | */
52 | onHide: function () {
53 |
54 | },
55 |
56 | /**
57 | * 生命周期函数--监听页面卸载
58 | */
59 | onUnload: function () {
60 |
61 | },
62 |
63 | /**
64 | * 页面相关事件处理函数--监听用户下拉动作
65 | */
66 | onPullDownRefresh: function () {
67 |
68 | },
69 |
70 | /**
71 | * 页面上拉触底事件的处理函数
72 | */
73 | onReachBottom: function () {
74 |
75 | },
76 |
77 | /**
78 | * 用户点击右上角分享
79 | */
80 | onShareAppMessage: function () {
81 |
82 | }
83 | })
--------------------------------------------------------------------------------
/微信客户端/pages/recommedation_configuration/recommedation_configuration.json:
--------------------------------------------------------------------------------
1 | {
2 | "usingComponents": {}
3 | }
4 |
5 |
--------------------------------------------------------------------------------
/微信客户端/pages/recommedation_configuration/recommedation_configuration.wxml:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/微信客户端/pages/recommedation_configuration/recommedation_configuration.wxss:
--------------------------------------------------------------------------------
1 | /* pages/recommedation_configuration/recommedation_configuration.wxss */
2 | page {
3 | background: #00BFFF;
4 | height: 100%;
5 | }
6 |
7 | .button {
8 | width: 250rpx;
9 | height: 90rpx;
10 | marigin:0rpx auto;
11 | background-color: rgb(252, 126, 67);
12 | color: white;
13 | border-radius: 98rpx;
14 | background: bg_red;
15 | }
16 |
17 | .section{
18 | margin-top: 5px;
19 | margin-left: 20px;
20 | margin-right: 20px;
21 | height: 35px;
22 | border: 2px solid red;
23 | border-radius: 25px;
24 | }
25 | .input{
26 | padding-left: 10px;
27 | height: 30px;
28 | }
29 |
30 | .title {
31 | margin-bottom: 20px;
32 | display: flex;
33 | align-items: center;
34 | justify-content: center;
35 | }
36 |
37 | .name {
38 | color: #FAFFF0;
39 | font-size: 50rpx;
40 | }
41 |
42 | .container_cn {
43 | height: 100%;
44 | display: flex;
45 | flex-direction: column;
46 | align-items: center;
47 | justify-content: space-between;
48 | box-sizing: border-box;
49 | }
50 |
51 | .view-Name {
52 | color: #FFDEAD;
53 | font-size: 40rpx;
54 | display: flex;
55 | align-items: center;
56 | justify-content: center;
57 | }
58 |
59 | .view {
60 | margin: 40rpx;
61 | }
62 |
--------------------------------------------------------------------------------
/微信客户端/pages/select/select.js:
--------------------------------------------------------------------------------
1 | // pages/select/select.js
2 | const app = getApp()
3 | Page({
4 |
5 | /**
6 | * 页面的初始数据
7 | */
8 | data: {
9 |
10 | },
11 |
12 | /**
13 | * 生命周期函数--监听页面加载
14 | */
15 | onLoad: function (options) {
16 |
17 | },
18 |
19 | /**
20 | * 跳转到随机生成文件界面
21 | */
22 | random_file: function () {
23 | //界面跳转
24 | //界面跳转
25 | wx.navigateTo({
26 | url: '/pages/random_configuration/random_configuration'
27 | })
28 | },
29 |
30 | /**
31 | * 跳转到自定义文件上传界面
32 | */
33 | custom_file: function () {
34 | //界面跳转
35 | wx.navigateTo({
36 | url: '/pages/upload/upload'
37 | })
38 | },
39 |
40 | /**
41 | * 生命周期函数--监听页面初次渲染完成
42 | */
43 | onReady: function () {
44 |
45 | },
46 |
47 | /**
48 | * 生命周期函数--监听页面显示
49 | */
50 | onShow: function () {
51 |
52 | },
53 |
54 | /**
55 | * 生命周期函数--监听页面隐藏
56 | */
57 | onHide: function () {
58 |
59 | },
60 |
61 | /**
62 | * 生命周期函数--监听页面卸载
63 | */
64 | onUnload: function () {
65 |
66 | },
67 |
68 | /**
69 | * 页面相关事件处理函数--监听用户下拉动作
70 | */
71 | onPullDownRefresh: function () {
72 |
73 | },
74 |
75 | /**
76 | * 页面上拉触底事件的处理函数
77 | */
78 | onReachBottom: function () {
79 |
80 | },
81 |
82 | /**
83 | * 用户点击右上角分享
84 | */
85 | onShareAppMessage: function () {
86 |
87 | }
88 | })
--------------------------------------------------------------------------------
/微信客户端/pages/select/select.json:
--------------------------------------------------------------------------------
1 | {
2 | "usingComponents": {}
3 | }
--------------------------------------------------------------------------------
/微信客户端/pages/select/select.wxml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/微信客户端/pages/select/select.wxss:
--------------------------------------------------------------------------------
1 | /* pages/select/select.wxss */
2 | page {
3 | background: #00BFFF;
4 | height: 100%;
5 | }
6 |
7 | /* 按下变颜色 */
8 | .hover {
9 | background: rgb(236, 179, 156);
10 | }
11 |
12 | .button {
13 | width: 400rpx;
14 | height: 100rpx;
15 | margin: 10rpx;
16 | background-color: rgb(252, 126, 67);
17 | color: white;
18 | border-radius: 98rpx;
19 | background: bg_red;
20 | }
21 |
22 | .container_cn {
23 | height: 100%;
24 | display: flex;
25 | flex-direction: column;
26 | align-items: center;
27 | justify-content: space-between;
28 | padding: 450rpx 0;
29 | box-sizing: border-box;
30 | }
--------------------------------------------------------------------------------
/微信客户端/pages/upload/upload.js:
--------------------------------------------------------------------------------
1 | // pages/upload/upload.js
2 | const app = getApp()
3 | Page({
4 |
5 | /**
6 | * 页面的初始数据
7 | */
8 | data: {
9 |
10 | },
11 |
12 | /**
13 | * 生命周期函数--监听页面加载
14 | */
15 | onLoad: function (options) {
16 |
17 | },
18 |
19 | /**
20 | * 上传文件按钮函数,
21 | * 上传文件并返回结果,
22 | * 跳转到recommedation界面展示结果
23 | */
24 | upload_file: function () {
25 | //获取推荐参数配置
26 | var recommend_configuration = app.globalData.recommend_configuration
27 | console.log(recommend_configuration)
28 |
29 | //目前微信还未开放上传文本等文件的能力
30 | wx.showToast({
31 | title: '正在开发,请耐心等待',
32 | icon: 'none',
33 | duration: 2000
34 | })
35 | },
36 |
37 | /**
38 | * 生命周期函数--监听页面初次渲染完成
39 | */
40 | onReady: function () {
41 |
42 | },
43 |
44 | /**
45 | * 生命周期函数--监听页面显示
46 | */
47 | onShow: function () {
48 |
49 | },
50 |
51 | /**
52 | * 生命周期函数--监听页面隐藏
53 | */
54 | onHide: function () {
55 |
56 | },
57 |
58 | /**
59 | * 生命周期函数--监听页面卸载
60 | */
61 | onUnload: function () {
62 |
63 | },
64 |
65 | /**
66 | * 页面相关事件处理函数--监听用户下拉动作
67 | */
68 | onPullDownRefresh: function () {
69 |
70 | },
71 |
72 | /**
73 | * 页面上拉触底事件的处理函数
74 | */
75 | onReachBottom: function () {
76 |
77 | },
78 |
79 | /**
80 | * 用户点击右上角分享
81 | */
82 | onShareAppMessage: function () {
83 |
84 | }
85 | })
--------------------------------------------------------------------------------
/微信客户端/pages/upload/upload.json:
--------------------------------------------------------------------------------
1 | {
2 | "usingComponents": {}
3 | }
--------------------------------------------------------------------------------
/微信客户端/pages/upload/upload.wxml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/微信客户端/pages/upload/upload.wxss:
--------------------------------------------------------------------------------
1 | /* pages/upload/upload.wxss */
2 | page {
3 | background: #00BFFF;
4 | height: 100%;
5 | }
6 |
7 | /* 按下变颜色 */
8 | .hover {
9 | background: rgb(236, 179, 156);
10 | }
11 |
12 | .button {
13 | width: 400rpx;
14 | height: 100rpx;
15 | margin: 10rpx;
16 | background-color: rgb(252, 126, 67);
17 | color: white;
18 | border-radius: 98rpx;
19 | background: bg_red;
20 | }
--------------------------------------------------------------------------------
/微信客户端/project.config.json:
--------------------------------------------------------------------------------
1 | {
2 | "description": "项目配置文件",
3 | "packOptions": {
4 | "ignore": []
5 | },
6 | "setting": {
7 | "urlCheck": true,
8 | "es6": true,
9 | "postcss": true,
10 | "minified": true,
11 | "newFeature": true,
12 | "autoAudits": false
13 | },
14 | "compileType": "miniprogram",
15 | "libVersion": "2.6.6",
16 | "appid": "wxfa929dd31067ae5c",
17 | "projectname": "RecommendationSystem",
18 | "debugOptions": {
19 | "hidedInDevtools": []
20 | },
21 | "isGameTourist": false,
22 | "condition": {
23 | "search": {
24 | "current": -1,
25 | "list": []
26 | },
27 | "conversation": {
28 | "current": -1,
29 | "list": []
30 | },
31 | "game": {
32 | "currentL": -1,
33 | "list": []
34 | },
35 | "miniprogram": {
36 | "current": -1,
37 | "list": []
38 | }
39 | }
40 | }
--------------------------------------------------------------------------------
/微信客户端/utils/util.js:
--------------------------------------------------------------------------------
1 | const formatTime = date => {
2 | const year = date.getFullYear()
3 | const month = date.getMonth() + 1
4 | const day = date.getDate()
5 | const hour = date.getHours()
6 | const minute = date.getMinutes()
7 | const second = date.getSeconds()
8 |
9 | return [year, month, day].map(formatNumber).join('/') + ' ' + [hour, minute, second].map(formatNumber).join(':')
10 | }
11 |
12 | const formatNumber = n => {
13 | n = n.toString()
14 | return n[1] ? n : '0' + n
15 | }
16 |
17 | module.exports = {
18 | formatTime: formatTime
19 | }
20 |
--------------------------------------------------------------------------------
/服务端/.DS_Store:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/daimuuc/Recommendation-System/0cc9905f25c3a31a5f2768273dd59a3acfdfac1e/服务端/.DS_Store
--------------------------------------------------------------------------------
/服务端/.idea/misc.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/服务端/.idea/modules.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/服务端/.idea/workspace.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
69 |
70 |
71 |
72 | load_transactions
73 | dump_as_json
74 | dump_as_two_item_tsv
75 | TransactionManager
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 |
100 |
101 |
102 |
103 |
104 |
105 |
106 |
107 |
108 |
109 |
110 |
111 |
112 |
113 |
114 |
115 |
116 |
117 |
118 |
119 |
120 |
121 |
122 |
123 |
124 |
125 |
126 |
127 |
128 |
129 |
130 |
131 |
132 |
133 |
134 |
135 |
136 |
137 |
138 |
139 |
140 |
141 |
142 |
143 |
144 |
145 |
146 |
147 |
148 |
149 |
150 |
151 |
152 |
153 |
154 |
155 |
156 |
157 |
158 |
159 |
160 |
161 |
162 |
163 |
164 |
165 |
166 |
167 |
168 |
169 |
170 |
171 |
172 |
173 |
174 |
175 |
176 |
177 |
178 |
179 |
180 |
181 |
182 |
183 |
184 |
185 |
186 |
187 |
188 |
189 |
190 |
191 |
192 |
193 |
194 |
195 |
196 |
197 |
198 |
199 |
200 |
201 |
202 |
203 |
204 |
205 |
206 |
207 |
208 |
209 |
210 |
211 |
212 |
213 |
214 |
215 |
216 |
217 |
218 |
219 |
220 |
221 |
222 |
223 |
224 |
225 |
226 |
227 |
228 |
229 |
230 |
231 |
232 |
233 |
234 |
235 |
236 |
237 |
238 |
239 |
240 |
241 |
242 | 1556281724226
243 |
244 |
245 | 1556281724226
246 |
247 |
248 |
249 |
250 |
251 |
252 |
253 |
254 |
255 |
256 |
257 |
258 |
259 |
260 |
261 |
262 |
263 |
264 |
265 |
266 |
267 |
268 |
269 |
270 |
271 |
272 |
273 |
274 |
275 |
276 |
277 |
278 |
279 |
280 |
281 |
282 |
283 |
284 |
285 |
286 |
287 |
288 |
289 |
290 |
291 |
292 |
293 |
294 |
295 |
296 |
297 |
298 |
299 |
300 |
301 |
302 |
303 |
304 |
305 |
306 |
307 |
308 |
309 |
310 |
311 |
312 |
313 |
314 |
315 |
316 |
317 |
318 |
319 |
320 |
321 |
322 |
323 |
324 |
325 |
326 |
327 |
328 |
329 |
330 |
331 |
332 |
333 |
334 |
335 |
336 |
337 |
338 |
339 |
340 |
341 |
342 |
343 |
344 |
345 |
346 |
347 |
348 |
349 |
350 |
351 |
352 |
353 |
354 |
355 |
356 |
357 |
358 |
359 |
360 |
361 |
362 |
363 |
364 |
365 |
366 |
367 |
368 |
369 |
370 |
371 |
372 |
373 |
374 |
375 |
376 |
377 |
378 |
379 |
380 |
381 |
382 |
383 |
384 |
385 |
386 |
387 |
388 |
389 |
390 |
391 |
392 |
393 |
394 |
395 |
396 |
397 |
398 |
399 |
400 |
401 |
402 |
403 |
404 |
405 |
406 |
407 |
408 |
409 |
410 |
411 |
412 |
413 |
414 |
415 |
416 |
417 |
418 |
419 |
420 |
421 |
422 |
423 |
424 |
425 |
426 |
427 |
428 |
429 |
430 |
431 |
432 |
433 |
434 |
435 |
436 |
437 |
438 |
439 |
440 |
441 |
442 |
443 |
444 |
445 |
446 |
447 |
448 |
449 |
450 |
451 |
452 |
453 |
454 |
455 |
456 |
457 |
458 |
459 |
460 |
461 |
462 |
463 |
464 |
465 |
466 |
467 |
468 |
469 |
470 |
471 |
472 |
473 |
474 |
475 |
476 |
477 |
478 |
479 |
480 |
481 |
482 |
483 |
484 |
485 |
486 |
487 |
488 |
489 |
490 |
491 |
492 |
493 |
494 |
495 |
496 |
497 |
498 |
499 |
500 |
501 |
502 |
503 |
504 |
505 |
506 |
507 |
508 |
509 |
510 |
511 |
512 |
513 |
514 |
515 |
516 |
517 |
518 |
519 |
520 |
521 |
522 |
523 |
524 |
525 |
526 |
527 |
528 |
529 |
530 |
531 |
532 |
533 |
534 |
535 |
536 |
537 |
538 |
539 |
540 |
541 |
542 |
543 |
544 |
545 |
546 |
547 |
548 |
549 |
550 |
551 |
552 |
553 |
554 |
555 |
556 |
557 |
558 |
559 |
560 |
561 |
562 |
563 |
564 |
565 |
566 |
567 |
568 |
569 |
570 |
571 |
572 |
573 |
574 |
575 |
576 |
577 |
578 |
579 |
580 |
581 |
582 |
583 |
584 |
585 |
586 |
587 |
588 |
589 |
590 |
591 |
592 |
593 |
594 |
595 |
596 |
597 |
598 |
599 |
600 |
601 |
602 |
603 |
604 |
605 |
606 |
607 |
608 |
609 |
610 |
611 |
612 |
613 |
614 |
615 |
616 |
617 |
618 |
619 |
620 |
621 |
622 |
623 |
624 |
625 |
626 |
627 |
628 |
629 |
630 |
631 |
632 |
633 |
634 |
635 |
636 |
637 |
638 |
639 |
640 |
641 |
642 |
643 |
644 |
645 |
646 |
647 |
648 |
649 |
650 |
651 |
652 |
653 |
654 |
655 |
656 |
657 |
658 |
659 |
660 |
661 |
662 |
663 |
664 |
665 |
666 |
667 |
668 |
669 |
670 |
671 |
--------------------------------------------------------------------------------
/服务端/.idea/推荐系统.iml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/服务端/Readme.md:
--------------------------------------------------------------------------------
1 | 项目名称:
2 | 音乐推荐系统
3 | 项目功能:
4 | 基于Apriori算法实现对音乐类型推荐
5 | 项目结构:
6 | |----- uploads(客户端上传文件存储目录)
7 | |
8 | |----- apriori.py (实现Apriori算法)
9 | |
10 | |----- client.py (模拟客户端请求)
11 | |
12 | |----- config.py (配置文件)
13 | |
14 | |----- recommendation.py(基于Apriori算法实现推荐功能.)
15 | |
16 | |----- server.py(基于Flask的服务端API)
17 | |
18 | |----- utils.py(常用工具函数)
19 | 项目部署:
20 | --Linux
21 | 1、修改config.py文件(配置ssl)
22 | 2、运行server.py文件
23 | 3、修改client.py文件(配置域名)
24 | --Windows、mac
25 | 1、修改server.py文件(取消ssl配置)
26 | 2、修改clien.py文件(配置域名)
27 |
--------------------------------------------------------------------------------
/服务端/__pycache__/apriori.cpython-36.pyc:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/daimuuc/Recommendation-System/0cc9905f25c3a31a5f2768273dd59a3acfdfac1e/服务端/__pycache__/apriori.cpython-36.pyc
--------------------------------------------------------------------------------
/服务端/__pycache__/config.cpython-36.pyc:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/daimuuc/Recommendation-System/0cc9905f25c3a31a5f2768273dd59a3acfdfac1e/服务端/__pycache__/config.cpython-36.pyc
--------------------------------------------------------------------------------
/服务端/__pycache__/recommendation.cpython-36.pyc:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/daimuuc/Recommendation-System/0cc9905f25c3a31a5f2768273dd59a3acfdfac1e/服务端/__pycache__/recommendation.cpython-36.pyc
--------------------------------------------------------------------------------
/服务端/__pycache__/utils.cpython-36.pyc:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/daimuuc/Recommendation-System/0cc9905f25c3a31a5f2768273dd59a3acfdfac1e/服务端/__pycache__/utils.cpython-36.pyc
--------------------------------------------------------------------------------
/服务端/apriori.py:
--------------------------------------------------------------------------------
1 | #-*- coding: UTF-8 -*-
2 | """
3 | 实现Apriori算法.
4 | 直接调用apriori函数即可.
5 | API Usage:
6 | transactions = [
7 | ['beer', 'nuts'],
8 | ['beer', 'cheese'],
9 | ]
10 | results = list(apriori(transactions))
11 |
12 | 关于Apriori算法的概念参考
13 | 博客https://westerly-lzh.github.io/cn/2015/08/DM001-Apriori/.
14 |
15 | """
16 |
17 | from __future__ import print_function, division
18 | from collections import namedtuple
19 | from itertools import combinations
20 |
21 | ################################################################################
22 | # 数据结构
23 | ################################################################################
24 | class TransactionManager(object):
25 | """
26 | 事务管理器.
27 | """
28 |
29 | def __init__(self, transactions):
30 | """
31 | 初始化函数.
32 |
33 | :param
34 | transactions -- 一个可迭代事务对象
35 | (eg. [['A', 'B'], ['B', 'C']]).
36 | """
37 | self.__num_transaction = 0
38 | self.__items = []
39 | self.__transaction_index_map = {}
40 |
41 | for transaction in transactions:
42 | self.add_transaction(transaction)
43 |
44 | def add_transaction(self, transaction):
45 | """
46 | 增加一个事务
47 |
48 | :param
49 | transaction -- 作为可迭代对象的事务 (eg. ['A', 'B']).
50 | """
51 | for item in transaction:
52 | if item not in self.__transaction_index_map:
53 | self.__items.append(item)
54 | self.__transaction_index_map[item] = set()
55 | self.__transaction_index_map[item].add(self.__num_transaction)
56 | self.__num_transaction += 1
57 |
58 | def calc_support(self, items):
59 | """
60 | 返回项的支持度(support).
61 |
62 | :param
63 | items -- 作为可迭代对象的项 (eg. ['A', 'B']).
64 | """
65 | # 空项是被所有事务支持的.
66 | if not items:
67 | return 1.0
68 |
69 | # 空事务不支持任何项.
70 | if not self.num_transaction:
71 | return 0.0
72 |
73 | # 创建事务索引交集.
74 | sum_indexes = None
75 | for item in items:
76 | indexes = self.__transaction_index_map.get(item)
77 | if indexes is None:
78 | # 不支持包含不存在的项目的任何集合.
79 | return 0.0
80 |
81 | if sum_indexes is None:
82 | # 第一次分配索引.
83 | sum_indexes = indexes
84 | else:
85 | # 计算交集
86 | sum_indexes = sum_indexes.intersection(indexes)
87 |
88 | # 计算并返回支持度.
89 | return float(len(sum_indexes)) / self.__num_transaction
90 |
91 | def initial_candidates(self):
92 | """
93 | 返回初始候选项.
94 | """
95 | return [frozenset([item]) for item in self.items]
96 |
97 | @property
98 | def num_transaction(self):
99 | """
100 | 返回事务的个数.
101 | """
102 | return self.__num_transaction
103 |
104 | @property
105 | def items(self):
106 | """
107 | 返回由事务组成的项列表.
108 | """
109 | return sorted(self.__items)
110 |
111 | @staticmethod
112 | def create(transactions):
113 | """
114 | 创建TransactionManager实例.
115 | 如果给定实例就是TransactionManager实例,则返回本身
116 | """
117 | if isinstance(transactions, TransactionManager):
118 | return transactions
119 | return TransactionManager(transactions)
120 |
121 |
122 | # 忽略名称错误,因为这些名称属于名称元组.
123 | SupportRecord = namedtuple( # pylint: disable=C0103
124 | 'SupportRecord', ('items', 'support'))
125 | RelationRecord = namedtuple( # pylint: disable=C0103
126 | 'RelationRecord', SupportRecord._fields + ('ordered_statistics',))
127 | OrderedStatistic = namedtuple( # pylint: disable=C0103
128 | 'OrderedStatistic', ('items_base', 'items_add', 'confidence', 'lift',))
129 |
130 |
131 | ################################################################################
132 | # 内部函数.
133 | ################################################################################
134 | def create_next_candidates(prev_candidates, length):
135 | """
136 | 以列表的形式返回apriori候选项
137 |
138 | :param
139 | prev_candidates -- 以列表形式的以前候选项.
140 | length -- 下一个候选想的长度.
141 | """
142 | # 处理项并排序.
143 | item_set = set()
144 | for candidate in prev_candidates:
145 | for item in candidate:
146 | item_set.add(item)
147 | items = sorted(item_set)
148 |
149 | # 创建暂时的候选项.
150 | tmp_next_candidates = (frozenset(x) for x in combinations(items, length))
151 |
152 | # 如果下一个候选项的长度为2,则返回所有候选项
153 | # 因为它们的子集与项相同.
154 | if length < 3:
155 | return list(tmp_next_candidates)
156 |
157 | # 过滤所有子集在之前候选项的项.
158 | next_candidates = [
159 | candidate for candidate in tmp_next_candidates
160 | if all(
161 | True if frozenset(x) in prev_candidates else False
162 | for x in combinations(candidate, length - 1))
163 | ]
164 | return next_candidates
165 |
166 |
167 | def gen_support_records(transaction_manager, min_support, **kwargs):
168 | """
169 | 返回给定事务的支持度(support)记录生成器.
170 |
171 | :param
172 | transaction_manager -- TransactionManager实例.
173 | min_support -- 最小支持度(support)(float).
174 |
175 | Keyword arguments:
176 | max_length -- 关系最大长度(integer).
177 | """
178 | # 解析参数.
179 | max_length = kwargs.get('max_length')
180 |
181 | # 用于测试.
182 | _create_next_candidates = kwargs.get(
183 | '_create_next_candidates', create_next_candidates)
184 |
185 | # 处理.
186 | candidates = transaction_manager.initial_candidates()
187 | length = 1
188 | while candidates:
189 | relations = set()
190 | for relation_candidate in candidates:
191 | support = transaction_manager.calc_support(relation_candidate)
192 | if support < min_support:
193 | continue
194 | candidate_set = frozenset(relation_candidate)
195 | relations.add(candidate_set)
196 | yield SupportRecord(candidate_set, support)
197 | length += 1
198 | if max_length and length > max_length:
199 | break
200 | candidates = _create_next_candidates(relations, length)
201 |
202 |
203 | def gen_ordered_statistics(transaction_manager, record):
204 | """
205 | 将有序统计信息的生成器作为OrderedStatistic实例返回.
206 |
207 | :param
208 | transaction_manager -- TransactionManager实例.
209 | record -- SupportRecord实例.
210 | """
211 | items = record.items
212 | for combination_set in combinations(sorted(items), len(items) - 1):
213 | items_base = frozenset(combination_set)
214 | items_add = frozenset(items.difference(items_base))
215 | confidence = (
216 | record.support / transaction_manager.calc_support(items_base))
217 | lift = confidence / transaction_manager.calc_support(items_add)
218 | yield OrderedStatistic(
219 | frozenset(items_base), frozenset(items_add), confidence, lift)
220 |
221 |
222 | def filter_ordered_statistics(ordered_statistics, **kwargs):
223 | """
224 | 过滤OrderedStatistic对象.
225 |
226 | :param
227 | ordered_statistics -- 可迭代OrderedStatistic对象.
228 |
229 | Keyword arguments:
230 | min_confidence -- 最小关系置信度值(confidence)(float).
231 | min_lift -- 最小关系提升度值(lift)(float).
232 | """
233 | min_confidence = kwargs.get('min_confidence', 0.0)
234 | min_lift = kwargs.get('min_lift', 0.0)
235 |
236 | for ordered_statistic in ordered_statistics:
237 | if ordered_statistic.confidence < min_confidence:
238 | continue
239 | if ordered_statistic.lift < min_lift:
240 | continue
241 | yield ordered_statistic
242 |
243 |
244 | ################################################################################
245 | # API 函数.
246 | ################################################################################
247 | def apriori(transactions, **kwargs):
248 | """
249 | 执行Apriori算法并返回RelationRecor生成器.
250 |
251 | Arguments:
252 | transactions -- 可迭代事务对象
253 | (eg. [['A', 'B'], ['B', 'C']]).
254 |
255 | Keyword arguments:
256 | min_support -- 最小关系支持度值(support)(float).
257 | min_confidence -- 最小关系置信度值(confidence)(float).
258 | min_lift -- 最小关系提升度值(lift)(float).
259 | max_length -- 关系最大长度(integer).
260 | """
261 | # 解析参数.
262 | min_support = kwargs.get('min_support', 0.1)
263 | min_confidence = kwargs.get('min_confidence', 0.0)
264 | min_lift = kwargs.get('min_lift', 0.0)
265 | max_length = kwargs.get('max_length', None)
266 |
267 | # 检查参数.
268 | if min_support <= 0:
269 | raise ValueError('minimum support must be > 0')
270 |
271 | # 用于测试.
272 | _gen_support_records = kwargs.get(
273 | '_gen_support_records', gen_support_records)
274 | _gen_ordered_statistics = kwargs.get(
275 | '_gen_ordered_statistics', gen_ordered_statistics)
276 | _filter_ordered_statistics = kwargs.get(
277 | '_filter_ordered_statistics', filter_ordered_statistics)
278 |
279 | # 计算支持度.
280 | transaction_manager = TransactionManager.create(transactions)
281 | support_records = _gen_support_records(
282 | transaction_manager, min_support, max_length=max_length)
283 |
284 | # 计算有序统计数据.
285 | for support_record in support_records:
286 | ordered_statistics = list(
287 | _filter_ordered_statistics(
288 | _gen_ordered_statistics(transaction_manager, support_record),
289 | min_confidence=min_confidence,
290 | min_lift=min_lift,
291 | )
292 | )
293 | if not ordered_statistics:
294 | continue
295 | yield RelationRecord(
296 | support_record.items, support_record.support, ordered_statistics)
--------------------------------------------------------------------------------
/服务端/client.py:
--------------------------------------------------------------------------------
1 | #-*- coding: UTF-8 -*-
2 | """
3 | 模拟客户端请求,测试服务端3个API功能.
4 | """
5 |
6 | from __future__ import print_function, division
7 | import requests
8 |
9 | ################################################################################
10 | #函数入口
11 | ################################################################################
12 | if __name__ == '__main__':
13 | # 配置信息
14 | configuration = {
15 | 'min_support': 0.003, 'min_confidence' : 0.2,
16 | 'min_lift' : 3, 'max_length' : 5,
17 | 'num' : 10,
18 | 'data_num': 7500, 'data_max_length': 20
19 | }
20 |
21 | # 上传文件信息
22 | files = {
23 | 'file' : open('Music_Recommendation.csv', 'rb')
24 | }
25 |
26 | # 测试自定义文件请求API
27 | # results = requests.post('https://www.ponma.cn:5000/custom', \
28 | # data = configuration,
29 | # files = files)
30 |
31 | # 测试上传文件请求API
32 | # results = requests.post('https://www.ponma.cn:5000/upload',
33 | # files = files)
34 |
35 | # 测试随机文件请求API
36 | results = requests.get("https://www.ponma.cn:5000/random", params = configuration)
37 |
38 | # 显示结果
39 | results = results.json()
40 | for k, v in results.items():
41 | print(k, ' ', v)
42 |
--------------------------------------------------------------------------------
/服务端/config.py:
--------------------------------------------------------------------------------
1 | """
2 | 配置文件
3 | ssl配置
4 | """
5 |
6 | ################################################################################
7 | #ssl配置,可以参考https://blog.csdn.net/robin912/article/details/80698896.
8 | ################################################################################
9 | pem = '你的文件地址'
10 | key = '你的文件地址'
--------------------------------------------------------------------------------
/服务端/recommendation.py:
--------------------------------------------------------------------------------
1 | #-*- coding: UTF-8 -*-
2 | """
3 | 基于Apriori算法实现推荐功能.
4 | 训练关联规则模型(Apriori)以查找过去一段时间用户所听音乐类型
5 | 的最相关项.该算法将大多数用户的产品偏好关联起来,并可用于生成音乐类型
6 | 推荐.
7 | """
8 |
9 | from __future__ import print_function, division
10 | import pandas as pd
11 | from apriori import apriori
12 | import json
13 | import collections
14 |
15 | ################################################################################
16 | #基于Apriori算法实现推荐功能
17 | ################################################################################
18 | def recommedation(path = './Music_Recommendation.csv', **kwargs):
19 | """
20 | 基于Apriori算法实现推荐功能.
21 | 按照lift得分排序,推荐得分最高的前n个组合,
22 | 既打印结果,也以json形式返回结果.
23 |
24 | :param
25 | path -- 文件路径(str),文件必须是csv文件.
26 | :keyword
27 | min_support -- 最小关系支持度值(support)(float).
28 | min_confidence -- 最小关系置信度值(confidence)(float).
29 | min_lift -- 最小关系提升度值(lift)(float).
30 | max_length -- 关系最大长度(int).
31 | num -- 推荐lift得分最高的前num个组合,即推荐个数(int).
32 | """
33 | # 解析参数
34 | min_support = kwargs.get('min_support', 0.1)
35 | min_confidence = kwargs.get('min_confidence', 0.0)
36 | min_lift = kwargs.get('min_lift', 0.0)
37 | max_length = kwargs.get('max_length', None)
38 | num = kwargs.get('num', 10)
39 |
40 | # 加载数据
41 | dataset = pd.read_csv(path, names = list(range(0, 20)))
42 | # 将每一条用户数据以列表的形式加入列表
43 | transactions = []
44 | for i in range(0, len(dataset)):
45 | vals = []
46 | for j in range(0, len(dataset.keys())):
47 | val = str(dataset.values[i,j])
48 | # 判断是否是空值,如果是空值,省略
49 | if val != 'nan':
50 | vals.append(val)
51 | transactions.append(vals)
52 |
53 | # 调用Apriori算法预测结果
54 | rules = apriori(transactions, min_support = min_support, min_confidence = min_confidence, min_lift = min_lift,
55 | max_length = max_length)
56 |
57 | # 可视化结果
58 | results = list(rules)
59 | lift = []
60 | association = []
61 | for i in range (0, len(results)):
62 | lift.append(results[:len(results)][i][2][0][3])
63 | association.append(list(results[:len(results)][i][0]))
64 | rank = pd.DataFrame([association, lift]).T
65 | rank.columns = ['Association', 'Lift']
66 | # 按照lift得分排序,推荐得分最高的前n个组合
67 | results = rank.sort_values('Lift', ascending=False)
68 | # 满足条件的结果个数
69 | size = len(results)
70 | # 如果满足条件的结果个数小于要求的输出个数,
71 | # 则输出全部结果
72 | if size < num:
73 | num = size
74 | # 打印结果
75 | print(results.head(num))
76 |
77 | # 将结果以json格式返回
78 | # 有序字典
79 | results_json = collections.OrderedDict()
80 | for i in range(num):
81 | results_json[str(results.iloc[i, : ]['Association'])] = \
82 | results.iloc[i, :]['Lift']
83 | return json.dumps(results_json)
84 |
85 | ################################################################################
86 | #函数入口
87 | ################################################################################
88 | if __name__ == '__main__':
89 | # 文件路径(str),文件必须是csv文件
90 | # 默认路径
91 | # path = './Music_Recommendation.csv'
92 | # 随机生成文件路径
93 | path = './Music_Recommendation_Random.csv'
94 |
95 | # 基于Apriori算法实现推荐功能.
96 | results_json = recommedation(path, min_support = 0.003, min_confidence = 0.2, min_lift = 3, max_length = 5, num = 10)
97 | print(results_json)
--------------------------------------------------------------------------------
/服务端/server.py:
--------------------------------------------------------------------------------
1 | #-*- coding: UTF-8 -*-
2 | """
3 | 基于Flask的服务端API.
4 | process_random:
5 | 响应随机生成数据请求.
6 | process_custom:
7 | 响应自定义数据请求.
8 | upload_file:
9 | 上传自定义文件
10 | """
11 |
12 | from __future__ import print_function, division
13 | from flask import Flask, request, jsonify
14 | from utils import random_generate_data
15 | from recommendation import recommedation
16 | from werkzeug.utils import secure_filename
17 | import os
18 | import config
19 |
20 | # 创建Flask类实例
21 | app = Flask(__name__)
22 |
23 | ################################################################################
24 | # 响应随机生成数据请求,请求方式为get.
25 | ################################################################################
26 | @app.route('/random', methods = ['get'])
27 | def process_random():
28 | """
29 | 响应随机生成数据请求,以json格式返回结果
30 | """
31 | try:
32 | # 文件位置
33 | path = './Music_Recommendation_Random.csv'
34 |
35 | # 解析随机生成数据需要的参数
36 | # 随机生data_num条数据(int)
37 | data_num = int(request.args['data_num'])
38 | # 每条数据最多拥有data_max_length个音乐类型(int), data_max_length <= 20.
39 | data_max_length = int(request.args['data_max_length'])
40 |
41 | # 解析推荐需要的参数
42 | # 最小关系支持度值(support)(float)
43 | min_support = float(request.args['min_support'])
44 | # 最小关系置信度值(confidence)(float).
45 | min_confidence = float(request.args['min_confidence'])
46 | # 最小关系提升度值(lift)(float).
47 | min_lift = float(request.args['min_lift'])
48 | # 关系最大长度(int).
49 | max_length = int(request.args['max_length'])
50 | # 推荐lift得分最高的前num个组合,即推荐个数(int).
51 | num = int(request.args['num'])
52 |
53 | # 随机生成用户音乐类型数据
54 | random_generate_data(num=data_num, max_length=data_max_length)
55 |
56 | # 基于Apriori算法实现推荐功能.
57 | results_json = recommedation(path, min_support=min_support, min_confidence=min_confidence, \
58 | min_lift=min_lift, max_length=max_length, num=num)
59 |
60 | return results_json
61 | except Exception:
62 | results_json = {}
63 | results_json['status'] = 'Failure'
64 | return jsonify(results_json)
65 |
66 | ################################################################################
67 | # 响应自定义数据请求,请求方式为post.
68 | ################################################################################
69 | @app.route('/custom', methods=['post'])
70 | def process_custom():
71 | """
72 | 响应自定义数据请求,以json格式返回结果
73 | """
74 | try:
75 | # 解析参数
76 | # 最小关系支持度值(support)(float)
77 | min_support = float(request.form['min_support'])
78 | # 最小关系置信度值(confidence)(float).
79 | min_confidence = float(request.form['min_confidence'])
80 | # 最小关系提升度值(lift)(float).
81 | min_lift = float(request.form['min_lift'])
82 | # 关系最大长度(int).
83 | max_length = int(request.form['max_length'])
84 | # 推荐lift得分最高的前num个组合,即推荐个数(int).
85 | num = int(request.form['num'])
86 |
87 | # 存储上传文件的目录地址
88 | UPLOAD_FOLDER = './uploads'
89 |
90 | # 读取并保存上传文件
91 | file = request.files['file']
92 | # 判断文件是否存在和格式是否正确
93 | if file and allowed_file(file.filename):
94 | filename = secure_filename(file.filename)
95 | # 文件存储路径
96 | path = os.path.join(UPLOAD_FOLDER, filename)
97 | file.save(path)
98 |
99 | # 基于Apriori算法实现推荐功能.
100 | results_json = recommedation(path, min_support=min_support, min_confidence=min_confidence, \
101 | min_lift=min_lift, max_length=max_length, num=num)
102 | return results_json
103 | else:
104 | raise Exception
105 | except Exception:
106 | results_json = {}
107 | results_json['status'] = 'Failure'
108 | return jsonify(results_json)
109 |
110 | ################################################################################
111 | # 上传自定义文件,请求方式为post.
112 | ################################################################################
113 | @app.route('/upload', methods=['post'])
114 | def upload_file():
115 | """
116 | 上传自定义文件,以json形式返回上传状态
117 | """
118 | try:
119 | # 存储上传文件的目录地址
120 | UPLOAD_FOLDER = './uploads'
121 |
122 | # 读取并保存上传文件
123 | file = request.files['file']
124 | # 判断文件是否存在和格式是否正确
125 | if file and allowed_file(file.filename):
126 | filename = secure_filename(file.filename)
127 | # 文件存储路径
128 | path = os.path.join(UPLOAD_FOLDER, filename)
129 | file.save(path)
130 |
131 | # 返回结果
132 | results_json = {}
133 | results_json['status'] = 'Success'
134 | return jsonify(results_json)
135 | else:
136 | raise Exception
137 | except Exception:
138 | results_json = {}
139 | results_json['status'] = 'Failure'
140 | return jsonify(results_json)
141 |
142 | ################################################################################
143 | # 内部函数,判断文件格式是否正确.
144 | ################################################################################
145 | def allowed_file(filename):
146 | """
147 | 判断文件格式是否正确,以boolean形式返回结果
148 | :param
149 | filename -- 文件名(str)
150 | """
151 | # 文件格式
152 | ALLOWED_EXTENSIONS = set(['txt', 'pdf', 'png', 'jpg', 'jpeg', 'gif', 'csv'])
153 | return '.' in filename and \
154 | filename.rsplit('.', 1)[1] in ALLOWED_EXTENSIONS
155 |
156 | ################################################################################
157 | # 函数入口.
158 | ################################################################################
159 | if __name__ == '__main__':
160 | # ssl配置文件地址,可以参考https://blog.csdn.net/robin912/article/details/80698896
161 | pem = config.pem
162 | key = config.key
163 |
164 | # 运行程序并设置外部可访问
165 | app.run(port = 5000, host='0.0.0.0', debug = False, ssl_context = (pem, key))
--------------------------------------------------------------------------------
/服务端/utils.py:
--------------------------------------------------------------------------------
1 | #-*- coding: UTF-8 -*-
2 | """
3 | 常用工具函数
4 | random_generate_data:
5 | 随机生成用户音乐类型数据,以csv文件保存
6 | show_data_info:
7 | 展示random_generate_data函数生成的csv文件相关统计信息
8 | """
9 |
10 | from __future__ import print_function, division
11 | import random
12 | import csv
13 | import pandas as pd
14 |
15 | ################################################################################
16 | # 随机生成用户音乐类型数据,以csv文件保存,
17 | # 指定保存路径为./Music_Recommendation_Random.csv,
18 | # 文件中每一条记录表示为一个用户近期所听音乐类型.
19 | ################################################################################
20 | def random_generate_data(**kwargs):
21 | """
22 | 随机生成用户音乐类型数据
23 |
24 | :kwargs
25 | num -- 随机生num条数据(int).
26 | max_length -- 每条数据最多拥有max_length个音乐类型(int), max_length <= 20.
27 | """
28 |
29 | # 解析参数
30 | num = kwargs.get('num', 7500)
31 | max_length = kwargs.get('max_length', 20)
32 |
33 | # 指定csv文件保存路径
34 | path = './Music_Recommendation_Random.csv'
35 |
36 | # 音乐种类,共计120种,来源https://www.musicgenreslist.com/
37 | music = ['Art Punk', 'Alternative Rock', 'Britpunk', 'College Rock', 'Crossover Thrash', 'Crust Punk',
38 | 'Emotional Hardcore',
39 | 'Experimental Rock', 'Folk Punk', 'Goth / Gothic Rock', 'Grunge', 'Hardcore Punk', 'Hard Rock',
40 | 'Indie Rock', 'Lo-fi',
41 | 'Musique Concrète', 'New Wave', 'Progressive Rock', 'Punk', 'Shoegaze', 'Steampunk', 'Anime',
42 | 'Acoustic Blues', 'African Blues',
43 | 'Blues Rock', 'Blues Shouter', 'British Blues', 'Canadian Blues', 'Chicago Blues', 'Classic Blues',
44 | 'Classic Female Blues',
45 | 'Contemporary Blues', 'Contemporary R&B', 'Country Blues', 'Delta Blues', 'Detroit Blues',
46 | 'Electric Blues', 'Folk Blues', 'Gospel Blues',
47 | 'Harmonica Blues', 'Hill Country Blues', 'Hokum Blues', 'Jazz Blues', 'Jump Blues', 'Kansas City Blues',
48 | 'Louisiana Blues', 'Memphis Blues',
49 | 'Modern Blues', 'New Orlean Blues', 'NY Blues', 'Piano Blues', 'Piedmont Blues', 'Punk Blues',
50 | 'Ragtime Blues', 'Rhythm Blues',
51 | 'Soul Blues', 'Lullabies', 'Sing-Along', 'Stories', 'Avant-Garde', 'Ballet', 'Baroque', 'Cantata',
52 | 'Chamber Music', 'Chant', 'Choral',
53 | 'Classical Crossover', 'Concerto', 'Concerto Grosso', 'Contemporary Classical', 'Early Music',
54 | 'Expressionist', 'High Classical', 'Impressionist',
55 | 'Mass Requiem', 'Medieval', 'Minimalism', 'Modern Composition', 'Modern Classical', 'Opera', 'Oratorio',
56 | 'Orchestral', 'Organum', 'Renaissance',
57 | 'Romantic', 'Sonata', 'Symphonic', 'Symphony', 'Wedding Music', 'Novelty', 'Parody Music ',
58 | 'Stand-up Comedy', 'Vaudeville', 'Jingles', 'TV Themes',
59 | 'Alternative Country', 'Americana', 'Australian Country', 'Bakersfield Sound', 'Bluegrass',
60 | 'Blues Country', 'Cajun Fiddle Tunes', 'Christian Country',
61 | 'Classic Country', 'Close Harmony', 'Contemporary Bluegrass', 'Contemporary Country', 'Country Gospel',
62 | 'Country Pop', 'Country Rap', 'Country Rock',
63 | 'Country Soul', 'Cowboy', 'Cowpunk', 'Dansband', 'Honky Tonk', 'Franco-Country', 'Gulf and Western',
64 | 'Hellbilly Music', 'Honky Tonk']
65 | # 统一格式为'utf-8'
66 | temp = []
67 | for val in music:
68 | temp.append(val.encode('utf-8'))
69 | music = temp
70 |
71 | # 随机生成用户音乐类型数据.
72 | out = open(path, 'w', newline='')
73 | csv_write = csv.writer(out, dialect='excel')
74 | for i in range(num):
75 | # 随机生成该条数据拥有音乐类型个数
76 | length = random.randint(1, max_length)
77 | # 随机生成含有length个音乐类型的记录
78 | vals = random.sample(music, length)
79 | csv_write.writerow(vals)
80 | out.close()
81 | print("write over")
82 |
83 | ################################################################################
84 | #展示random_generate_data函数生成的csv文件相关统计信息.
85 | ################################################################################
86 | def show_data_info():
87 | """
88 | 展示random_generate_data函数生成的csv文件相关统计信息.
89 | """
90 | # 文件路径
91 | path = './Music_Recommendation_Random.csv'
92 |
93 | # 加载数据
94 | dataset = pd.read_csv(path, names = list(range(0, 20)))
95 |
96 | # 统计文件中每个音乐类型出现的次数
97 | pairs = {}
98 | # 统计文件中每个音乐类型出现的次数之和
99 | total = 0
100 | for i in range(len(dataset)):
101 | for j in range(len(dataset.keys())):
102 | if str(dataset.values[i, j]) != 'nan':
103 | total += 1
104 | if dataset.values[i, j] in pairs.keys():
105 | pairs[dataset.values[i, j]] += 1
106 | else:
107 | pairs[dataset.values[i, j]] = 1
108 |
109 | # 展示统计结果
110 | print('统计结果如下')
111 | for k, v in pairs.items():
112 | print("%5s%5d" %(k, v))
113 | print('\n%5s %5d' %('total', total))
114 |
115 | ################################################################################
116 | #函数入口
117 | ################################################################################
118 | if __name__ == '__main__':
119 | # 随机生成用户音乐类型数据.
120 | # random_generate_data(num = 7500, max_length = 20)
121 |
122 | # 展示random_generate_data函数生成的csv文件相关统计信息.
123 | show_data_info()
124 |
--------------------------------------------------------------------------------
/演示视屏.mp4:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/daimuuc/Recommendation-System/0cc9905f25c3a31a5f2768273dd59a3acfdfac1e/演示视屏.mp4
--------------------------------------------------------------------------------
/项目结构图.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/daimuuc/Recommendation-System/0cc9905f25c3a31a5f2768273dd59a3acfdfac1e/项目结构图.png
--------------------------------------------------------------------------------