基於協同過濾的推薦算法JavaScript實現


把下面的源碼放到一個js文件里,例如命名:index.js;

1.安裝依賴:npm i lodash --save //這是一個格式化數據的庫

2.使用時導入即可:import { RecommendUserService, RecommendGoodsService } from index.js //兩套算法,一套基於用戶,一套基於物品

3.兩套算法使用方式相似:

一、基於用戶的算法(RecommendUserService),參數有三個data,userId,n //前兩個參數必選,第三個可選

data格式[{userId: 1, goodsId: 11},{userId:2,goodsId:222}...] //此數據為用戶物品倒排表,就是每有用戶購買一個商品就生成 一條一對一數據

userId是一個字符串或數字,是生成個性化推薦所面向的用戶ID

n是正整數,是取相似度最高的前n個用戶,這個值不固定,目前大網站的數據是10最准確

使用方式:const recommendUserService = new RecommendUserService(data, userId, n)

     const result = recommendUserService.start()

     // result是返回的結果,是一個一維數組:[goodsId1, goodsId2......],用戶對商品興趣度逆序排序的商品ID,也就是goodsId1是用戶興趣度最高的商品ID,以此類推

二、基於物品的算法(RecommendGoodsService),參數有三個data,userId,n //第一個參數必選,后面兩個可選

data格式[{userId: 1, goodsId: 11},{userId:2,goodsId:222}...] //此數據為物品用戶倒排表,就是每有一個商品被一個用戶購買就生成 一條一對一數據

userId是一個字符串或數字,是生成個性化推薦所面向的用戶ID

n是正整數,是取相似度最高的前n個商品,這個值不固定,目前大網站的數據是10最准確

使用方式:const recommendGoodsService = new RecommendGoodsService(data, userId, n) // 生成個性化推薦

     const result = recommendGoodsService.start()

     // result是返回的結果,是一個一維數組:[goodsId1, goodsId2......],用戶對商品興趣度逆序排序的商品ID,也就是goodsId1是用戶興趣度最高的商品ID,以此類推

     // 子系統,計算與商品goodsId最相似的前n個商品

     const recommendGoodsService = new RecommendGoodsService(data, userId, n)

     // userId為空則只計算商品相似度,不為空則排除掉自己買過的商品;goodsId則為計算目標

     const result = recommendGoodsService.getGoodsGrade(userId,  goodsId) 

     // result是返回的結果,是一個一維數組:[goodsId1, goodsId2......],是goodsId最相似的前n個商品的id列表

 

-------以下是源碼--------

const _ = require('lodash')
// 基於用戶推薦
const RecommendUserService = class RecommendUserService {
/**
* 構造方法
* @param {*倒查表所有數據組成的數組} data
* @param {*用戶ID} userId
* @param {*相似度最高的前n個} n
*/
constructor(data, userId, n) {
this.data = data
this.userId = userId
this.n = n
// 相似度的分子
this.top = undefined
// 相似度的分母
this.bottom = undefined
// 記錄除用戶自身外,其他所有用戶的ID
this.userArray = []
// 作為中間值,記錄當前正在進行的其他用戶的商品列表
this.userGoodsTemp = []
// 記錄用戶最終相似度列表
this.similarityList = []
// 記錄用戶相似度前n個用戶中所有購買的商品與本用戶不重復的商品
this.targetGoods = []
// 記錄用戶對篩選出的各商品感興趣程度
this.interestedGrade = [];
// 記錄最后的返回結果
this.result = []
}
/**
* 入口
*/
start() {
// 計算相似度,得出本用戶和其他所有用戶的相似度分數
this.getUserArray()
this.similarityList.sort((a, b) => {
return b.grade - a.grade
})
// 計算目標商品
this.getTargetGoods()
// 此時目標商品已存在this.targetGoods中, 然后去重
this.targetGoods = [...new Set(this.targetGoods)]

// 計算用戶對每個商品的感興趣程度
for (let goodsId of this.targetGoods.values()) {
this.getInterestedGrade(goodsId)
}
// 計算最終商品列表並逆序排序
this.getFinalResult()
return this.result
}
/**
* 計算最終商品列表並逆序排序
*/
getFinalResult() {
this.interestedGrade.sort((a, b) => {
return b.grade - a.grade
})
for (let obj of this.interestedGrade.values()) {
this.result.push(obj.goodsId)
}
}
/**
* 計算用戶對該商品的感興趣程度
* @param {*商品ID} goodsId
*/
getInterestedGrade(goodsId) {
// 篩選出對商品goodsId用過行為的用戶
let array = new Set()
for (let obj of this.data.values()) {
if (obj.goodsId === goodsId) {
array.add(obj.userId)
}
}
const users = [...array]
// 計算感興趣程度
let grade = 0
for (let userId of users.values()) {
for (let i = 0; i < this.n; i++) {
if (userId === this.similarityList[i].userId) {
const res = this.getUserSimilarity(userId, this.similarityList[i].userId)
grade += res
}
}
}
// 添加到最終結果
this.interestedGrade.push({
goodsId: goodsId,
grade: grade
})
}
/**
* 獲取目標商品數組
*/
getTargetGoods() {
// this.n > this.similarityList.length ? this.n = this.similarityList.l : this.n = this.n
// 截至目前,以獲取用戶相似度的逆序排序數組,以下為獲取前n個相似用戶購買的所有商品中本用戶沒買過的
for (let index = 0; index < this.n; index++) {
const element = this.similarityList[index]
_.filter(this.data, obj => {
if (obj.userId == element.userId) {
this.targetGoods.push(obj.goodsId)
}
return obj.userId == element.userId
})
}
// 去掉自身的商品,得到最終目標商品數組
this.duplicateRemovalGoods()

}
/**
* 去掉自身的商品,得到最終目標商品數組
*/
duplicateRemovalGoods() {
// 獲取當前用戶買過的商品
const userGoods = _.filter(this.data, obj => {
return obj.userId == this.userId
})
// 刪除本用戶買過的商品
for (let obj of userGoods.values()) {
if (this.targetGoods.includes(obj.goodsId)) {
this.targetGoods.splice(this.targetGoods.indexOf(obj.goodsId), 1)
}
}
}
/**
* 獲取除用戶自身外其他所有的用戶ID
*/
getUserArray() {
const data = _.filter(this.data, obj => {
return obj.userId !== this.userId
})
// 獲取其他所有用戶的ID
let arrayTemp = []
for (let index in data) {
arrayTemp.push(data[index].userId)
}
this.userArray = [...(new Set(arrayTemp))]
// 避免this.n超出邊界
this.n > this.userArray.length ? this.n = this.userArray.length : this.n = this.n
// 遍歷計算與每個用戶的相似度
for (let index in this.userArray) {
this.getUserSimilarity(this.userId, this.userArray[index])
}
}
/**
* 計算兩個用戶的相似度
* @param {*用戶ID} userId
* @param {*另一個用戶ID} otherUserId
*/
getUserSimilarity(userId, otherUserId) {
const userSelfGoods = _.filter(this.data, obj => {
return userId == obj.userId
})
this.filterUserGoods(otherUserId)
// 計算相似度的分母
this.bottom = Math.sqrt(userSelfGoods.length * this.userGoodsTemp.length)
// 記錄商品相似的個數
let count = 0
userSelfGoods.forEach(ele => {
for (let index in this.userGoodsTemp) {
if (ele.goodsId == this.userGoodsTemp[index].goodsId) {
// 懲罰熱門商品,計算懲罰參數
const log = this.filterGoodsById(ele.goodsId)
// 可在此處添加weight權重,log * weight
count += log
}
}
})
this.top = count
const obj = {
userId: otherUserId,
grade: this.top / this.bottom
}
this.similarityList.push(obj)
return obj.grade
}
/**
* 過濾出用戶otherUserId的商品列表
* @param {用戶ID} otherUserId
*/
filterUserGoods(otherUserId) {
this.userGoodsTemp = _.filter(this.data, obj => {
return obj.userId == otherUserId
})
}
/**
* 過濾出商品goodsId的商品列表
* @param {商品ID} goodsId
*/
filterGoodsById(goodsId) {
const goods = _.filter(this.data, obj => {
return obj.goodsId == goodsId
})
return 1 / Math.log(1 + goods.length)
}
}
// 基於物品推薦
const RecommendGoodsService = class RecommendGoodsService {
/**
* 構造方法
* @param {*倒查表所有數據組成的數組} data
* @param {*商品ID} goodsId
* @param {*用戶ID} userId
* @param {*相似度最高的前k個} k
*/
constructor(data, userId, k, goodsId) {
this.data = data
this.goodsId = goodsId
this.userId = userId
// 篩選前k個商品······用於模塊一······
this.k = k
// 保存待計算商品列表······用於模塊一······
this.goodsList = []
// 保存當前商品的購買人列表······用於模塊一······
this.users = []
// 保存當前商品相似度列表······用於模塊一······
this.simpleList = []
// 開啟第二子系統-模塊二
// 保存當前人喜愛商品列表
this.userPerferList = []
// 保存當前人沒買過的商品列表
this.goodsMayPerferList = []
// 保存推薦結果並排序
this.resultRank = []
// 最終結果
this.result = []
}

/**
* 入口
*/
start() {
// 獲取待計算數據
this.getInitialData()
// 開始計算用戶對未買過的商品感興趣程度
for (let goodsId of this.goodsMayPerferList.values()) {
const res = this.getUserInterest(goodsId)
this.resultRank.push(res)
}
// 逆序排序
this.resultRank.sort((a, b) => {
return b.grade - a.grade
})
// 獲取最終結果
this.result = this.resultRank.reduce((array, obj) => {
array.push(obj.goodsId)
return array
}, [])
return this.result
}
/**
* 計算用戶對該商品的感興趣程度
* @param {*商品ID} goodsId
*/
getUserInterest(goodsId) {
// 獲取goodsId相似的商品列表
const simple = this.getGoodsGrade(false, goodsId)
let grade = 0
for (let [index, obj] of simple.entries()) {
if (this.userPerferList.includes(obj.goodsId) && index < this.k) {
grade += obj.grade
}
}
return { goodsId, grade }
}
/**
* 獲取待計算數據
*/
getInitialData() {
// 獲取當前人的喜愛記錄
this.userPerferList = this.data.reduce((array, obj) => {
if (obj.userId === this.userId && !array.includes(obj.goodsId)) {
array.push(obj.goodsId)
}
return array
}, [])
// 獲取當前用戶沒買過的商品列表
this.goodsMayPerferList = this.data.reduce((array, obj) => {
if (!array.includes(obj.goodsId) && !this.userPerferList.includes(obj.goodsId)) {
array.push(obj.goodsId)
}
return array
}, [])
}
/**
* 計算與商品goodsId相似的前k個商品列表,······模塊一······
* @param {*是否去掉自身相關的商品} isDelSelf
* @param {*商品ID} goodsId
*/
getGoodsGrade(isDelSelf, goodsId) {
this.simpleList = []
this.goodsId = goodsId
// 獲取待計算商品列表
this.getGoodsList()
// 獲取當前商品的購買人列表
this.users = this.getGoodsUserNum(this.goodsId)
// 計算相似度
for (let goodsId of this.goodsList.values()) {
this.getGoodsSimple(goodsId)
}
// 根據相似度排序
this.simpleList.sort((a, b) => {
//倒序
return b.grade - a.grade
})
// 是否排除掉自身
if (isDelSelf) {
this.getNotSelfGoods()
}
// 相似度歸一化
this.gradeNormalization()
return this.simpleList
}
/**
* 獲取目標商品數組
*/
getGoodsList() {
//篩選除了本商品之外的商品數據
const goodsArray = this.data.reduce((array, obj) => {
if (obj.goodsId !== this.goodsId) {
array.push(obj.goodsId)
}
return array
}, [])
//數組去重並解構
const goods = [...new Set(goodsArray)]
// 得到目標商品列表
this.goodsList = goods
}
/**
* 去掉已買過的商品,得到目標商品數組
*/
getNotSelfGoods() {
// 篩選當前用戶買過的商品
const userGoods = this.data.reduce((array, obj) => {
if (obj.userId === this.userId) {
array.push(obj.goodsId)
}
return array
}, [])
// 刪除本用戶買過的商品
for (let [index, obj] of this.simpleList.entries()) {
if (userGoods.includes(obj.goodsId)) {
this.simpleList.splice(index, 1)
}
}
}
/**
* 獲取商品相似度列表
* @param {商品ID} goodsId
*/
getGoodsSimple(goodsId) {
const users = this.getGoodsUserNum(goodsId)
// 計算相似度的分母
const bottom = Math.sqrt(this.users.length * users.length)
let count = 0
// 計算兩個商品的共同用戶數,得到相識度的分子
for (let val of users.values()) {
if (this.users.includes(val)) {
// 懲罰活躍用戶
count += this.getSimpleElememt(val)
}
}
// 保存結果對象,包括商品ID和相似度
const res = {
goodsId,
grade: count / bottom
}
this.simpleList.push(res)
}
/**
* 提升算法,懲罰活躍用戶,計算相似度分子
* @param {*用戶ID} userId
*/
getSimpleElememt(userId) {
// 找到用戶買過的商品數量
const goodsNum = this.data.reduce((array, obj) => {
if (obj.userId === userId) {
array.push(obj.goodsId)
}
return array
}, [])
const count = [...new Set(goodsNum)].length
const element = 1 / Math.log(1 + count)
return element
}
/**
* 獲取商品的購買人
* @param {*商品ID} goodsId
*/
getGoodsUserNum(goodsId) {
//得到商品的購買人
const users = this.data.reduce((array, obj) => {
if (obj.goodsId === goodsId) {
array.push(obj.userId)
}
return array
}, [])
return [...new Set(users)];
}
/**
* 相似度歸一化
*/
gradeNormalization() {
// 取最大值
const max = this.simpleList[0].grade
for (let index of this.simpleList.keys()) {
this.simpleList[index].grade = this.simpleList[index].grade / max
}
}
}

module.exports = { RecommendUserService, RecommendGoodsService }


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM