Hệ thống recommend bài nhạc

song-recommendations
song-recommendations

Một trong những công nghệ giúp cho đời sống con người ngày càng dễ dàng hơn đó là recommender system. Recommender system giúp kết nối người dùng với sản phẩm mà họ tìm kiếm được thuận tiện và nhanh chóng hơn từ đó mang lại lợi thế cạnh tranh của sản phẩm so với các đối thủ khác. Bạn có thể bắt gặp hệ thống này ở các trang xem phim, nghe nhạc, mua bán, mạng xã hội, … Về cơ bản, bạn có thể áp dụng ngay kỹ thuật này vào hệ thống của bạn thông qua các hướng tiếp cận như: Popularity – liệt kê top các sản phẩm được nhiều người quan tâm nhất, Classification – dựa vào các chủ đề mà bạn cung cấp để lọc ra danh sách sản phẩm tương ứng. Tuy nhiên, các hướng tiếp cận này đều mang tính đại chúng, không nhắm vào một cá nhân cụ thể nào. Hơn nữa, không ai dễ dàng cung cấp thông tin cá nhân cho ứng dụng của bạn để có thể lọc thông tin phù hợp.

Do đó, trong bài viết này, tôi sẽ đi theo hướng tiếp cận Collaborative Filtering với hai phương pháp gồm Memory-Based Collaborative Filtering và Model-Based Collaborative filtering giúp trả lời hai câu hỏi “user nghe bài nhạc này thì sẽ có xu hướng nghe các bài như…” và “user có gu âm nhạc như bạn thì sẽ có xu hướng nghe các bài nhạc như…”. Trong đó, Model-Based sẽ sử dụng singular value decomposition (SVD) và Memory-Based sử dụng khoảng cách cosine để mô hình hóa hệ thống. Bạn có thể download dữ liệu từ đây Million Song Dataset Challenge.

Source code: Github.

Chuẩn bị tập dữ liệu

Đầu tiên, ta sẽ import các thư viện cần thiết. Nếu có thư viện nào bị thiếu, bạn có thể sử dụng pip để install.

import os
from math import sqrt

import numpy as np
import pandas as pd
from scipy.sparse.linalg import svds
from sklearn.metrics import mean_squared_error
from sklearn.metrics.pairwise import pairwise_distances
from sklearn.model_selection import train_test_split

Tiếp theo, ta sẽ đọc tập dữ liệu và quan sát tổng quan.

def load_music_data(file_name):
    """Get reviews data, from local csv."""
    if os.path.exists(file_name):
        print("-- " + file_name + " found locally")
        df = pd.read_csv(file_name)

    return df

# Load music data
song_data = load_music_data("song_data.csv")    

print "-- Explore data"
print song_data.head()

n_users = song_data.user_id.unique().shape[0]
n_items = song_data.song_id.unique().shape[0]
print "Number of users = " + str(n_users) + " | Number of songs = " + str(n_items)

-- Explore data
                                    user_id             song_id  listen_count
0  b80344d063b5ccb3212f76538f3d9e43d87dca9e  SOAKIMP12A8C130995             1
1  b80344d063b5ccb3212f76538f3d9e43d87dca9e  SOBBMDR12A8C13253B             2
2  b80344d063b5ccb3212f76538f3d9e43d87dca9e  SOBXHDL12A81C204C0             1
3  b80344d063b5ccb3212f76538f3d9e43d87dca9e  SOBYHAJ12A6701BF1D             1
4  b80344d063b5ccb3212f76538f3d9e43d87dca9e  SODACBL12A8C13C273             1

Number of users = 66346 | Number of songs = 10000

Nếu bạn áp dụng phương pháp popularity, nghĩa là hiển thị top các bài nhạc được nghe nhiều nhất. Ta có thể thực hiện như sau:

print "-- Showing the most popular songs in the dataset"
unique, counts = np.unique(song_data["song"], return_counts=True)
popular_songs = dict(zip(unique, counts))
df_popular_songs = pd.DataFrame(popular_songs.items(), columns=["Song", "Count"])
df_popular_songs = df_popular_songs.sort_values(by=["Count"], ascending=False)
print df_popular_songs.head()

-- Showing the most popular songs in the dataset
                                                   Song  Count
2616                           Sehr kosmisch - Harmonia   5970
4482                                       Undo - Björk   5281
5567                    You\'re The One - Dwight Yoakam   4806
7358  Dog Days Are Over (Radio Edit) - Florence + Th...   4536
940                             Revelry - Kings Of Leon   4339

Có thể thấy Sehr kosmisch – Harmonia được nghe nhiều nhất với 5970 lượt nghe. Dù bạn là ai đi chăng nữa thì hệ thống của chúng ta cũng chỉ recommend cho người dùng bằng cách hiển thị top các bài nhạc được nghe nhiều nhất. Giả sử người dùng không thích các thể loại nhạc này thì hệ thống của chúng ta đã thất bại trong việc gợi ý.

Trước khi xây dựng hệ thống recommender, ta sẽ phân chia tập dữ liệu thành tập train và tập test với tỉ lệ 0.75/0.25.

train_data, test_data = train_test_split(song_data, test_size=0.25)

Memory-Based Collaborative Filtering

Để có thể áp dụng phương pháp user-item filtering và item-item filtering, ta cần xây dựng ma trận user-item như hình bên dưới:

user-item-matrix
user-item-matrix

Nếu tập dữ liệu của chúng ta có 66,346 user và 10,000 bài nhạc thì ta sẽ đi xây dựng ma trận user-item 66,346 dòng và 10,000 cột dữ liệu. Như vậy, ta có thể biết được số lượt nghe (listen count/ratings) của một user đối với một bài nhạc thông qua việc truy xuất ma trận như hình bên dưới.

similarity-items
similarity-items

Ta sẽ tiến hành chuyển đổi mã user và mã bài nhạc sang chỉ số của ma trận như sau:

def values_to_map_index(values):
    map_index = {}
    idx = 0
    for val in values:
        map_index[val] = idx
        idx += 1

    return map_index

user_idx = values_to_map_index(song_data.user_id.unique())
song_idx = values_to_map_index(song_data.song_id.unique())

Khi đó, ta có thể xây dựng được hai ma trận user-item, một cho train dataset, hai cho test dataset. Lưu ý, line[1] là mã user, line[2] là mã bài nhạc, line[3] là số lượt nghe.

train_data_matrix = np.zeros((n_users, n_items))
for line in train_data.itertuples():
    train_data_matrix[user_idx[line[1]], song_idx[line[2]]] = line[3]

test_data_matrix = np.zeros((n_users, n_items))
for line in test_data.itertuples():
    test_data_matrix[user_idx[line[1]], song_idx[line[2]]] = line[3]

Sau khi xây dựng được ma trận user-item, ta sẽ xây dựng ma trận khoảng cách để tính độ tương tự (similarity distance) giữa các item và user lẫn nhau. Ta có nhiều độ đo khoảng cách để áp dụng tính toán. Thông thường, ta sử dụng độ đo khoảng cách cosine để tính.

Cosine similiarity giữa user k và a được tính dựa vào công thức như bên dưới. Trong đó, x_{k,m} là số lượt nghe bài nhạc m của user k, x_{a,m} là số lượt nghe bài nhạc m của user a.

s^{cos}_u (u_k, u_a) = \frac{u_k \cdot u_a}{||u_k|| ||u_a||} = \frac{\sum x_{k,m} x_{a,m}}{\sqrt{\sum x^2_{k,m} x^2_{a,m}}}

Tương tự, ta có độ đo khoảng cách giữa item m và b như bên dưới:

s^{cos}_u (i_m, i_b) = \frac{i_m \cdot i_b}{||i_m|| ||i_b||} = \frac{\sum x_{a,m} x_{a,b}}{\sqrt{\sum x^2_{a,m} x^2_{a,b}}}

Ta có thể dùng hàm pairwise_distances của sklearn để tính cosine similarity. Chú ý, output sẽ nằm trong khoảng 0 đến 1 do số lượt nghe đều là số không âm.

user_similarity = pairwise_distances(train_data_matrix, metric='cosine', n_jobs=-1)
item_similarity = pairwise_distances(train_data_matrix.T, metric='cosine', n_jobs=-1)

Để có thể dự đoán số lượt nghe bài nhạc m của user k. Số lượt nghe càng cao tương đương với user quan tâm đến bài nhạc này càng nhiều. Ta có thể sắp xếp lại kết quả dự đoán để cho ra các bài nhạc gợi ý cho user này. Công thức dự đoán theo user-based CF được tính như bên dưới:

\hat x_{k,m} = \bar x_k + \frac{\sum_{u_a} sim_u(u_k, u_a)(x_{a,m} - \bar x_{u_a})}{\sum_{u_a} |sim_u(u_k, u_a)|}

Ta có thể thấy độ đo khoảng cách giữa user k và a được sử dụng như trọng số của mô hình dự đoán. User k càng giống user a bao nhiêu thì kết quả dự đoán sẽ càng gần với a bấy nhiêu. Đồng thời, giá trị dự đoán sẽ được tính dựa vào số lượt nghe trung bình của user k đối với các bài nhạc trước đó. Tương tự, item-based CF được tính như sau:

\hat x_{k,m} = \frac{\sum_{i_b} sim_i(i_m, i_b)(x_{k,b})}{\sum_{i_b} |sim_i(i_m, i_b)|}

def predict(ratings, similarity, type='user'):
    if type == 'user':
        mean_user_rating = ratings.mean(axis=1)
        # You use np.newaxis so that mean_user_rating has same format as ratings
        ratings_diff = (ratings - mean_user_rating[:, np.newaxis])
        pred = mean_user_rating[:, np.newaxis] + similarity.dot(ratings_diff) / np.array(
            [np.abs(similarity).sum(axis=1)]).T
    elif type == 'item':
        pred = ratings.dot(similarity) / np.array([np.abs(similarity).sum(axis=1)])
    return pred

item_prediction = predict(train_data_matrix, item_similarity, type='item')
user_prediction = predict(train_data_matrix, user_similarity, type='user')

Cuối cùng, ta sẽ sử dụng Root Mean Squared Error (RMSE) để đánh giá mô hình dựa vào tập dữ liệu test.

RMSE = \sqrt{\frac{1}{N} \sum (x_i - \hat x_i)^2}

sklearn có cung cấp hàm mean_square_error (MSE) để tính toán. Do ta đánh giá mô hình dựa vào tập test nên ta cần filter prediction matrix trước bằng lệnh prediction[ground_truth.nonzero()]. Giá trị của RMSE càng nhỏ thì mô hình của chúng ta càng tốt.

def rmse(prediction, ground_truth):
    prediction = prediction[ground_truth.nonzero()].flatten()
    ground_truth = ground_truth[ground_truth.nonzero()].flatten()
    return sqrt(mean_squared_error(prediction, ground_truth))

print 'User-based CF RMSE: ' + str(rmse(user_prediction, test_data_matrix))
print 'Item-based CF RMSE: ' + str(rmse(item_prediction, test_data_matrix))    

User-based CF RMSE: 6.10898815167
Item-based CF RMSE: 6.11872154544

Ta có thể thấy mô hình Memory-based dễ dàng cài đặt và dự đoán. Khuyết điểm của mô hình này đó là khó mở rộng cho các hệ thống lớn và không giải quyết được cold-start problem. Nghĩa là hệ thống không có khả năng dự đoán cho một user mới chưa có lượt nghe ở bất kỳ bài nhạc nào.

Model-based Collaborative Filtering

Giả sử, ta có d topics cho từng user và từng bài nhạc. Ví dụ, ta có thể mô tả bài nhạc m (R_m) thông qua phần trăm các topic bên trong đó như 0.3% jazz, 0.01% pop, 1.5% dance, … Tương tự, ta có thể mô tả gu âm nhạc của user u (L_u) thông qua phần trăm các topic như 2.5% jazz, 0% pop, 0.8% dance, … Khi đó, ta sẽ có hai vector tương tự như sau:

R_m = [0.3, 0.01, 1.5, ...], L_u = [2.5, 0, 0.8, ...]

Và mức độ quan tâm của user đối với bài nhạc này sẽ là tích vector của R_m, L_u. Khi đó, ta có thể sắp xếp các bài nhạc theo mức độ quan tâm này cho user.

matrix-factorization
matrix-factorization

Trong bài viết này, ta sẽ áp dụng Singular value decomposition (SVD) để phân tích ma trận user-item thành tích các ma trận thành phần (matrix factorization).

singular-value-decomposition
singular-value-decomposition

Để dự đoán, ta chỉ việc tính tích các ma trận lại với nhau.

svd-prediction
svd-prediction
# get SVD components from train matrix. Choose k, rank of S.
u, s, vt = svds(train_data_matrix, k=20)
s_diag_matrix = np.diag(s)
X_pred = np.dot(np.dot(u, s_diag_matrix), vt)
print 'User-based CF MSE: ' + str(rmse(X_pred, test_data_matrix))

User-based CF MSE: 6.10567510873

Model-based CF có khả năng mở rộng và giải quyết vấn đề ma trận bị rời rạc (sparsity level) tốt hơn memory-based models. Nhưng vẫn không giải quyết được vấn đề khi có user mới chưa có nghe bài nhạc nào. Bạn có thể giải quyết vấn đề cold-start bằng các cách gom nhóm người dùng mới vào nhóm người dùng cũ, dựa vào các feature ta đã xây dựng như thời điểm nghe nhạc, thông tin tài khoản, các chủ đề quan tâm, …

Hiện nay, bạn có thể cài đặt recommender system một cách dễ dàng hơn thông qua các thư viện hỗ trợ sẵn như Crab – Recommender systems in Python hay Spark Mlib cho các hệ thống Big Data.

Tham khảo thêm

Advertisements

2 thoughts on “Hệ thống recommend bài nhạc

Gửi phản hồi

Mời bạn điền thông tin vào ô dưới đây hoặc kích vào một biểu tượng để đăng nhập:

WordPress.com Logo

Bạn đang bình luận bằng tài khoản WordPress.com Log Out / Thay đổi )

Twitter picture

Bạn đang bình luận bằng tài khoản Twitter Log Out / Thay đổi )

Facebook photo

Bạn đang bình luận bằng tài khoản Facebook Log Out / Thay đổi )

Google+ photo

Bạn đang bình luận bằng tài khoản Google+ Log Out / Thay đổi )

Connecting to %s