KTV歌曲推薦-深入淺出協同過濾

前言

推薦算法有很多,最基礎的就是協同過濾,前段時間對KTV數據比較感興趣,大家去唱歌也只是唱熟悉的歌,那是不是有辦法給大家一些建議拓展一下唱歌的寬度呢。KTV推薦可能要考慮很多因素,比如唱歌者的音域,年齡,地區,喜好,等等。第一版算法暫時只從item base的角度出發去給用戶推薦。由於是個人興趣,所以沒有模型反饋迭代的過程,有興趣的可以自己實現。

協同過濾算法

協同過濾又叫行爲相似召回,其實就是基於共現的一種相似度計算。 Item Base的協同過濾算法有幾個關鍵概念:

相似度計算

相似度計算有很多種:共現相似度,歐幾里得距離,皮爾遜相關係數,等等這裏使用的是共現相似度,公式如下:

其中N(i)爲喜歡i歌曲的用戶數,同樣N(j)爲喜歡j歌曲的用戶數,分子爲同時喜歡i,j的用戶數。該公式爲改良公式,分子中加入了N(j)對相似度進行懲罰。這裏不細講。

ItemBase和UserBase

UserBase

尋找興趣相似的用戶,然後將偏好相同的用戶的歌曲推薦給被推薦用戶,表中發現A和C用戶都喜歡i和k歌曲所以兩個用戶相似,所以將C用戶的歌曲l推薦給A用戶。如果用共現的方式去表述就是。這裏細節計算的時候會涉及到用戶打分和相似用戶數據排序彙總。我這裏都是概述。

用戶/歌曲 歌曲i 歌曲j 歌曲k 歌曲l
用戶A 1   1 推薦
用戶B   1    
用戶C 1   1 1

ItemBase

與UserBase類似,計算相似的時候使用的是歌曲矩陣找到相似的歌曲,然後根據用戶歷史數據進行推薦,大概原理如下表。表中發現i,k歌曲同事被A,B兩個用戶喜歡,所以i,k相似,如果C用戶喜歡i歌曲那麼他應該也喜歡相似的k歌曲.

用戶/歌曲 歌曲i 歌曲j 歌曲k
用戶A 1   1
用戶B 1 1 1
用戶C 1   推薦

這裏使用的是ItemBase

算法實現

得到用戶對歌曲的one hot矩陣

  • 將歌曲去重,按歌名排序
  • 得到歌曲和索引的轉換字典

計算得到歌曲對歌曲的共現度矩陣

  • 計算共現矩陣

  • 計算單個歌曲的出現次數

  • 計算共現率值公式計算共現度

推薦

如果用戶喜歡i歌曲則

得到推薦歌曲爲k歌曲

代碼實現

獲取數據

import elasticsearch
import elasticsearch.helpers
import re
import numpy as np
import operator

def trim_song_name(song_name):
    """
    處理歌名,過濾掉無用內容和空白
    """
    song_name = song_name.strip()
    song_name = re.sub("-?【.*?】", "", song_name)
    song_name = re.sub("-?(.*?)", "", song_name)
    song_name = re.sub("-?(.*?)", "", song_name)
    return song_name

def get_data(size=0):
    """
    獲取uid=>作品名list的字典
    """
    cur_size=0
    ret = {}
    
    es_client = elasticsearch.Elasticsearch()
    search_result = elasticsearch.helpers.scan(
        es_client, 
        index="ktv_works", 
        doc_type="ktv_works", 
        scroll="10m",
        query={}
    )

    all_songs_list = []
    all_songs_set = set()
    for hit_item in search_result:
        cur_size += 1
        if size>0 and cur_size>size:
            break
            
        item = hit_item['_source']
        work_list = item['item_list']
        ret[item['uid']] = [trim_song_name(item['songname']) for item in work_list]
        
    return ret

def get_uniq_song_sort_list(song_dict):
    """
    合併重複歌曲並按歌曲名排序
    """
    return sorted(list(set(np.concatenate(list(song_dict.values())).tolist())))
    

相似度計算

import math

# 共現數矩陣
col_show_count_matrix = np.zeros((song_count, song_count))
one_trik_matrix = np.zeros(song_count)
for i in range(song_count):
    for j in range(song_count):
        if i>j: # 對角矩陣只計算一半的矩陣
            one_trik_matrix = np.zeros(song_count)
            one_trik_matrix[i] = 1
            one_trik_matrix[j] = 1
            
            ret_m = user_song_one_hot_matrix.dot(one_trik_matrix.T)
            col_show_value = len([ix for ix in ret_m if ix==2])
            col_show_count_matrix[i,j] = col_show_value
            col_show_count_matrix[j,i] = col_show_value

# 相似度矩陣
col_show_rate_matrix = np.zeros((song_count, song_count))

# 歌曲count N(i)矩陣
song_count_matrix = np.zeros(song_count)
for i in range(song_count):
    song_col = user_song_one_hot_matrix[:,i]
    song_count_matrix[i] = len([ix for ix in song_col if ix>=1])

# 相似度矩陣計算
for i in range(song_count):
    for j in range(song_count):
        if i>j: # 對角矩陣只計算一半的矩陣
            # 相似度計算 N(i)nN(j)/sqart(N(i)*N(j))
            rate_value = col_show_count_matrix[i,j]/math.sqrt(song_count_matrix[i]*song_count_matrix[j])
            col_show_rate_matrix[i,j] = rate_value
            col_show_rate_matrix[j,i] = rate_value

推薦

import operator

def get_songs_from_recommand(col_recommand_matrix):
    return [(int_to_song[k],r_value) for k,r_value in enumerate(col_recommand_matrix) if r_value>0]

input_song = "十年"
# 構造被推薦矩陣
one_trik_matrix = np.zeros(song_count)
one_trik_matrix[song_to_int[input_song]] = 1

col_recommand_matrix = col_show_rate_matrix.dot(one_trik_matrix.T)
recommand_array = get_songs_from_recommand(col_recommand_matrix)
sorted_x = sorted(recommand_array, key=lambda k:k[1], reverse=True)

# 獲取推薦結果
print(sorted_x)


結果

[('三生三世', 0.5773502691896258), ('下個路口見', 0.5773502691896258), ('不分手的戀愛', 0.5773502691896258),...]

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章