O conjunto de dados MovieLens
=============================
Existem vários conjuntos de dados disponíveis para pesquisa de
recomendação. Dentre eles, o conjunto de dados
`MovieLens `__ é provavelmente um dos mais
populares. MovieLens é um sistema de recomendação de filmes não
comercial baseado na web. Ele foi criado em 1997 e administrado pelo
GroupLens, um laboratório de pesquisa da Universidade de Minnesota, a
fim de coletar dados de classificação de filmes para fins de pesquisa.
Os dados do MovieLens têm sido críticos para vários estudos de pesquisa,
incluindo recomendação personalizada e psicologia social.
Obtendo os dados
----------------
O conjunto de dados MovieLens é hospedado pelo site
`GroupLens `__. Várias
versões estão disponíveis. Usaremos o conjunto de dados MovieLens 100K
:cite:`Herlocker.Konstan.Borchers.ea.1999`. Este conjunto de dados é
composto por classificações de :math:`100.000`, variando de 1 a 5
estrelas, de 943 usuários em 1.682 filmes. Ele foi limpo para que cada
usuário avaliasse pelo menos 20 filmes. Algumas informações demográficas
simples, como idade, sexo, gêneros dos usuários e itens também estão
disponíveis. Podemos baixar o
`ml-100k.zip `__
e extrair o arquivo ``u.data``, que contém todas as classificações
:math:`100.000` em o formato csv. Existem muitos outros arquivos na
pasta, uma descrição detalhada para cada arquivo pode ser encontrada no
arquivo
`README `__
do conjunto de dados .
Para começar, vamos importar os pacotes necessários para executar os
experimentos desta seção.
.. code:: python
import os
import pandas as pd
from mxnet import gluon, np
from d2l import mxnet as d2l
Em seguida, baixamos o conjunto de dados MovieLens 100k e carregamos as
interações como ``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
Estatísticas do conjunto de dados
---------------------------------
Vamos carregar os dados e inspecionar os primeiros cinco registros
manualmente. É uma maneira eficaz de aprender a estrutura de dados e
verificar se eles foram carregados corretamente.
.. 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
Podemos ver que cada linha consiste em quatro colunas, incluindo “id do
usuário” 1-943, “id do item” 1-1682, “classificação” 1-5 e “carimbo de
data/hora”. Podemos construir uma matriz de interação de tamanho
:math:`n \times m`, onde :math:`n` e :math:`m` são o número de usuários
e o número de itens, respectivamente. Este conjunto de dados registra
apenas as classificações existentes, portanto, também podemos chamá-lo
de matriz de classificação e usaremos a matriz de interação e a matriz
de classificação de forma intercambiável, caso os valores desta matriz
representem classificações exatas. A maioria dos valores na matriz de
classificação é desconhecida, pois os usuários não classificaram a
maioria dos filmes. Também mostramos a dispersão deste conjunto de
dados. A dispersão é definida como
``1 - número de entradas diferentes de zero / (número de usuários * número de itens)``.
Claramente, a matriz de interação é extremamente esparsa (ou seja,
esparsidade = 93,695%). Os conjuntos de dados do mundo real podem sofrer
com uma extensão maior de dispersão e tem sido um desafio de longa data
na construção de sistemas de recomendação. Uma solução viável é usar
informações secundárias adicionais, como recursos de usuário / item para
aliviar a dispersão.
Em seguida, plotamos a distribuição da contagem de classificações
diferentes. Como esperado, parece ser uma distribuição normal, com a
maioria das avaliações centrada em 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_140487_7_0.png
Dividindo o conjunto de dados
-----------------------------
Dividimos o conjunto de dados em conjuntos de treinamento e teste. A
função a seguir fornece dois modos de divisão, incluindo ``random`` e
``seq-aware``. No modo ``random``, a função divide as 100k interações
aleatoriamente sem considerar o carimbo de data / hora e usa 90% dos
dados como amostras de treinamento e os 10% restantes como amostras de
teste por padrão. No modo ``seq-aware``, deixamos de fora o item que um
usuário classificou mais recentemente para teste e o histórico de
interações dos usuários como conjunto de treinamento. As interações
históricas do usuário são classificadas do mais antigo ao mais novo com
base no carimbo de data / hora. Este modo será usado na seção de
recomendação com reconhecimento de sequência.
.. 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
Observe que é uma boa prática usar um conjunto de validação na prática,
além de apenas um conjunto de teste. No entanto, omitimos isso por uma
questão de brevidade. Nesse caso, nosso conjunto de teste pode ser
considerado como nosso conjunto de validação retido.
Carregando os dados
-------------------
Após a divisão do conjunto de dados, converteremos o conjunto de
treinamento e o conjunto de teste em listas e dicionários / matriz por
uma questão de conveniência. A função a seguir lê o quadro de dados
linha por linha e enumera o índice de usuários / itens começando do
zero. A função então retorna listas de usuários, itens, classificações e
um dicionário / matriz que registra as interações. Podemos especificar o
tipo de feedback para ``explícito`` ou ``implícito``.
.. 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
Posteriormente, colocamos as etapas acima juntas e elas serão usadas na
próxima seção. Os resultados são agrupados com ``Dataset`` e
``DataLoader``. Observe que o ``last_batch`` do ``DataLoader`` para
dados de treinamento é definido para o modo ``rollover`` (as amostras
restantes são transferidas para a próxima época) e os pedidos são
embaralhados.
.. 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
Sumário
-------
- Os conjuntos de dados MovieLens são amplamente usados para pesquisas
de recomendação. É público disponível e gratuito para usar.
- Definimos funções para baixar e pré-processar o conjunto de dados
MovieLens 100k para uso posterior em seções posteriores.
Exercícios
----------
- Que outros conjuntos de dados de recomendação semelhantes você pode
encontrar?
- Acesse o site https://movielens.org/ para obter mais informações
sobre MovieLens.
`Discussions `__
.. raw:: html