.. raw:: html Tập dữ liệu MovieLens ===================== .. raw:: html Có rất nhiều tập dữ liệu có sẵn dùng cho nghiên cứu hệ thống đề xuất. Trong số đó, tập dữ liệu `MovieLens `__ có lẽ là một trong những tập phổ biến nhất. MovieLens là một hệ thống đề xuất phim phi thương mại trên nền tảng web. Nó được tạo ra vào năm 1997 và vận hành bởi GroupLens, một phòng nghiên cứu tại Đại học Minnesota, nhằm thu thập dữ liệu đánh giá phim phục vụ mục đích nghiên cứu. MovieLens là một nguồn dữ liệu quan trọng cho các nghiên cứu về cá nhân hóa đề xuất và tâm lý học xã hội. .. raw:: html Tải Dữ liệu ----------- .. raw:: html Tập dữ liệu MovieLens có địa chỉ tại `GroupLens `__ với nhiều phiên bản khác nhau. Ở đây chúng ta sẽ sử dụng tập dữ liệu MovieLens 100K :cite:`Herlocker.Konstan.Borchers.ea.1999`. Tập dữ liệu này bao gồm :math:`100,000` đánh giá, xếp hạng từ 1 tới 5 sao, từ 943 người dùng dành cho 1682 phim. Nó được tiền xử lý sao cho mỗi người dùng đánh giá ít nhất 20 phim. Một vài thông tin nhân khẩu học cơ bản như tuổi và giới tính người dùng hay thể loại phim cũng được cung cấp. Ta có thể tải về `ml-100k.zip `__ và giải nén tệp ``u.data`` chứa toàn bộ :math:`100,000` đánh giá ở định dạng csv. Có nhiều tệp khác trong thư mục này, bản mô tả chi tiết cho mỗi tệp có thể được tìm thấy trong tệp `README `__ của tập dữ liệu. .. raw:: html Để bắt đầu, ta hãy nhập những gói thư viện cần thiết để chạy các thử nghiệm của phần này. .. code:: python from d2l import mxnet as d2l from mxnet import gluon, np import os import pandas as pd .. raw:: html Sau đó, ta tải tập dữ liệu MovieLens 100k và đưa về định dạng ``DataFrame``. .. code:: python #@save d2l.DATA_HUB['ml-100k'] = ( 'http://files.grouplens.org/datasets/movielens/ml-100k.zip', 'cd4dcac4241c8a4ad7badc7ca635da8a69dddb83') #@save def read_data_ml100k(): data_dir = d2l.download_extract('ml-100k') names = ['user_id', 'item_id', 'rating', 'timestamp'] data = pd.read_csv(os.path.join(data_dir, 'u.data'), '\t', names=names, engine='python') num_users = data.user_id.unique().shape[0] num_items = data.item_id.unique().shape[0] return data, num_users, num_items .. raw:: html Thống kê của Tập dữ liệu ------------------------ .. raw:: html Hãy nạp dữ liệu và quan sát năm bản ghi đầu tiên theo cách thủ công. Đây là một cách hiệu quả để học được cấu trúc dữ liệu cũng như chắc chắn rằng dữ liệu đã được nạp đúng. .. code:: python data, num_users, num_items = read_data_ml100k() sparsity = 1 - len(data) / (num_users * num_items) print(f'number of users: {num_users}, number of items: {num_items}') print(f'matrix sparsity: {sparsity:f}') print(data.head(5)) .. parsed-literal:: :class: output number of users: 943, number of items: 1682 matrix sparsity: 0.936953 user_id item_id rating timestamp 0 196 242 3 881250949 1 186 302 3 891717742 2 22 377 1 878887116 3 244 51 2 880606923 4 166 346 1 886397596 .. raw:: html Có thể thấy rằng mỗi dòng chứa bốn cột, bao gồm “user id” 1-943, “item id” 1-1682, “rating” 1-5 và “timestamp”. Ta có thể tạo ra một ma trận tương tác có kích thước :math:`n \times m`, với :math:`n` và :math:`m` lần lượt là số người dùng và số bộ phim. Tập dữ liệu này ghi lại các đánh giá đang tồn tại, vì thế ta có thể gọi nó là ma trận đánh giá. Ta sẽ sử dụng cả tên gọi ma trận tương tác và ma trận đánh giá trong trường hợp các giá trị của ma trận này biểu diễn chính xác các đánh giá. Hầu hết những giá trị trong ma trận đánh giá là chưa biết bởi đa số các bộ phim chưa được đánh giá bởi người dùng. Ta cũng có thể biểu diễn độ thưa thớt (*sparsity*) của tập dữ liệu này. Độ thưa thớt được định nghĩa là ``1 - số lượng các bản ghi khác không / ( số lượng người dùng * số lượng sản phẩm)``. Rõ ràng, ma trận tương tác là cực kỳ thưa thớt (độ thưa = 93.695%). Các tập dữ liệu trong thực tế thường có mức độ thưa thớt lớn hơn nhiều, và từ lâu đã trở thành thử thách trong việc xây dựng các hệ thống đề xuất. Một giải pháp khả thi đó là sử dụng các thông tin phụ như đặc trưng của người dùng/sản phẩm để giúp giảm bớt tác động từ tính thưa thớt. .. raw:: html Tiếp theo ta vẽ biểu đồ phân phối số lượng các đánh giá khác nhau. Đúng như mong đợi, nó trông giống một phân phối chuẩn, với hầu hết các đánh giá tập trung tại 3-4. .. code:: python d2l.plt.hist(data['rating'], bins=5, ec='black') d2l.plt.xlabel('Rating') d2l.plt.ylabel('Count') d2l.plt.title('Distribution of Ratings in MovieLens 100K') d2l.plt.show() .. figure:: output_movielens_vn_947d10_7_0.png .. raw:: html Chia tập Dữ liệu ---------------- .. raw:: html Ta chia tập dữ liệu thành tập huấn luyện và tập kiểm tra. Hàm dưới đây cung cấp hai chế độ chia bao gồm ``random`` và ``seq-aware``. Trong chế độ ``random``, dữ liệu 100k tương tác sẽ được chia một cách ngẫu nhiên mà không xét tới mốc thời gian, mặc định sử dụng 90% dữ liệu để làm mẫu huẫn luyện và 10% còn lại là mẫu kiểm tra. Trong chế độ ``seq-aware``, ta giữ lại sản phẩm mà người dùng đánh giá gần đây nhất cho tập kiểm tra, còn các tương tác trước đó sẽ được sử dụng cho tập huấn luyện. Lịch sử tương tác người dùng được sắp xếp từ cũ nhất tới mới nhất theo mốc thời gian. Chế độ này sẽ được sử dụng trong phần đề xuất có nhận thức về chuỗi. .. code:: python #@save def split_data_ml100k(data, num_users, num_items, split_mode='random', test_ratio=0.1): """Split the dataset in random mode or seq-aware mode.""" if split_mode == 'seq-aware': train_items, test_items, train_list = {}, {}, [] for line in data.itertuples(): u, i, rating, time = line[1], line[2], line[3], line[4] train_items.setdefault(u, []).append((u, i, rating, time)) if u not in test_items or test_items[u][-1] < time: test_items[u] = (i, rating, time) for u in range(1, num_users + 1): train_list.extend(sorted(train_items[u], key=lambda k: k[3])) test_data = [(key, *value) for key, value in test_items.items()] train_data = [item for item in train_list if item not in test_data] train_data = pd.DataFrame(train_data) test_data = pd.DataFrame(test_data) else: mask = [True if x == 1 else False for x in np.random.uniform( 0, 1, (len(data))) < 1 - test_ratio] neg_mask = [not x for x in mask] train_data, test_data = data[mask], data[neg_mask] return train_data, test_data .. raw:: html Lưu ý rằng trong thực tiễn, sử dụng một tập kiểm định tách biệt thay vì chỉ có một tập kiểm tra duy nhất là sự lựa chọn phù hợp. Tuy nhiên, chúng tôi bỏ qua điều này với mục đích cô đọng nội dung. Trong trường hợp này, ta có thể coi tập kiểm tra như một tập kiểm định bất đắc dĩ. .. raw:: html Nạp dữ liệu ----------- .. raw:: html Sau khi chia nhỏ tập dữ liệu, chúng ta sẽ biến đổi tập huấn luyện và tập kiểm tra thành các danh sách và từ điển/ma trận cho thuận tiện. Hàm dưới đây đọc dataframe vào theo từng dòng và duyệt qua từng chỉ mục của người dùng/sản phẩm bắt đầu từ 0. Tiếp đó nó trả về danh sách người dùng, sản phẩm, đánh giá và một từ điển/ma trận chứa các tương tác. Ta có thể chỉ rõ loại phản hồi là ``explicit`` (*trực tiếp*) hay ``implicit`` (*gián tiếp*). .. code:: python #@save def load_data_ml100k(data, num_users, num_items, feedback='explicit'): users, items, scores = [], [], [] inter = np.zeros((num_items, num_users)) if feedback == 'explicit' else {} for line in data.itertuples(): user_index, item_index = int(line[1] - 1), int(line[2] - 1) score = int(line[3]) if feedback == 'explicit' else 1 users.append(user_index) items.append(item_index) scores.append(score) if feedback == 'implicit': inter.setdefault(user_index, []).append(item_index) else: inter[item_index, user_index] = score return users, items, scores, inter .. raw:: html Cuối cùng ta kết hợp các bước trên lại để sử dụng ở phần tiếp theo. Kết quả được gói gọn trong ``Dataset`` và ``DataLoader``. Lưu ý rằng tham số ``last_batch`` của ``DataLoader`` dùng cho dữ liệu huấn luyện được thiếp lập ở chế độ ``rollover`` (các mẫu còn lại được đưa vào epoch tiếp theo) với thứ tự được xáo trộn. .. code:: python #@save def split_and_load_ml100k(split_mode='seq-aware', feedback='explicit', test_ratio=0.1, batch_size=256): data, num_users, num_items = read_data_ml100k() train_data, test_data = split_data_ml100k( data, num_users, num_items, split_mode, test_ratio) train_u, train_i, train_r, _ = load_data_ml100k( train_data, num_users, num_items, feedback) test_u, test_i, test_r, _ = load_data_ml100k( test_data, num_users, num_items, feedback) train_set = gluon.data.ArrayDataset( np.array(train_u), np.array(train_i), np.array(train_r)) test_set = gluon.data.ArrayDataset( np.array(test_u), np.array(test_i), np.array(test_r)) train_iter = gluon.data.DataLoader( train_set, shuffle=True, last_batch='rollover', batch_size=batch_size) test_iter = gluon.data.DataLoader( test_set, batch_size=batch_size) return num_users, num_items, train_iter, test_iter Tóm tắt ------- .. raw:: html - Tập dữ liệu MovieLens được sử dụng rộng rãi trong nghiên cứu hệ thống đề xuất. Đây là tập dữ liệu công khai và được sử dụng miễn phí. - Ta định nghĩa các hàm tải và tiền xử lý tập dữ liệu MovieLens 100k để sử dụng trong những phần tiếp theo. Bài tập ------- .. raw:: html - Bạn có thể tìm được tập dữ liệu đề xuất nào khác tương tự như tập MovieLens không? - Xem qua trang https://movielens.org/ để biết thêm thông tin về MovieLens. Thảo luận --------- - Tiếng Anh: `MXNet `__ - Tiếng Việt: `Diễn đàn Machine Learning Cơ Bản `__ Những người thực hiện --------------------- Bản dịch trong trang này được thực hiện bởi: - Đoàn Võ Duy Thanh - Trần Yến Thy - Đỗ Trường Giang - Nguyễn Văn Cường - Nguyễn Lê Quang Nhật - Lê Khắc Hồng Phúc *Cập nhật lần cuối: 06/10/2020. (Cập nhật lần cuối từ nội dung gốc: 17/07/2020)*