chore: import upstream snapshot with attribution
This commit is contained in:
@@ -0,0 +1,646 @@
|
||||
"""MovieLens dataset"""
|
||||
import os
|
||||
|
||||
import numpy as np
|
||||
import pandas as pd
|
||||
|
||||
from torch import LongTensor, Tensor
|
||||
|
||||
from ..base import dgl_warning
|
||||
from ..convert import heterograph
|
||||
from .dgl_dataset import DGLDataset
|
||||
|
||||
from .utils import (
|
||||
_get_dgl_url,
|
||||
download,
|
||||
extract_archive,
|
||||
load_graphs,
|
||||
load_info,
|
||||
save_graphs,
|
||||
save_info,
|
||||
split_dataset,
|
||||
)
|
||||
|
||||
GENRES_ML_100K = [
|
||||
"unknown",
|
||||
"Action",
|
||||
"Adventure",
|
||||
"Animation",
|
||||
"Children",
|
||||
"Comedy",
|
||||
"Crime",
|
||||
"Documentary",
|
||||
"Drama",
|
||||
"Fantasy",
|
||||
"Film-Noir",
|
||||
"Horror",
|
||||
"Musical",
|
||||
"Mystery",
|
||||
"Romance",
|
||||
"Sci-Fi",
|
||||
"Thriller",
|
||||
"War",
|
||||
"Western",
|
||||
]
|
||||
GENRES_ML_1M = GENRES_ML_100K[1:]
|
||||
GENRES_ML_10M = GENRES_ML_100K + ["IMAX"]
|
||||
|
||||
try:
|
||||
import torch
|
||||
except ImportError:
|
||||
HAS_TORCH = False
|
||||
else:
|
||||
HAS_TORCH = True
|
||||
|
||||
|
||||
def check_pytorch():
|
||||
"""Check if PyTorch is the backend."""
|
||||
if not HAS_TORCH:
|
||||
raise ModuleNotFoundError(
|
||||
"MovieLensDataset requires PyTorch to be the backend."
|
||||
)
|
||||
|
||||
|
||||
class MovieLensDataset(DGLDataset):
|
||||
r"""MovieLens dataset for edge prediction tasks. The raw datasets are extracted from
|
||||
`MovieLens <https://grouplens.org/datasets/movielens/>`, introduced by
|
||||
`Movielens unplugged: experiences with an occasionally connected recommender system <https://dl.acm.org/doi/10.1145/604045.604094>`.
|
||||
|
||||
The datasets consist of user ratings for movies and incorporate additional user/movie information in the form of features.
|
||||
The nodes represent users and movies, and the edges store ratings that users assign to movies.
|
||||
|
||||
Statistics:
|
||||
|
||||
MovieLens-100K (ml-100k)
|
||||
|
||||
- Users: 943
|
||||
- Movies: 1,682
|
||||
- Ratings: 100,000 (1, 2, 3, 4, 5)
|
||||
|
||||
MovieLens-1M (ml-1m)
|
||||
|
||||
- Users: 6,040
|
||||
- Movies: 3,706
|
||||
- Ratings: 1,000,209 (1, 2, 3, 4, 5)
|
||||
|
||||
MovieLens-10M (ml-10m)
|
||||
|
||||
- Users: 69,878
|
||||
- Movies: 10,677
|
||||
- Ratings: 10,000,054 (0.5, 1, 1.5, ..., 4.5, 5.0)
|
||||
|
||||
Parameters
|
||||
----------
|
||||
name: str
|
||||
Dataset name. (:obj:`"ml-100k"`, :obj:`"ml-1m"`, :obj:`"ml-10m"`).
|
||||
valid_ratio: int
|
||||
Ratio of validation samples out of the whole dataset. Should be in (0.0, 1.0).
|
||||
test_ratio: int, optional
|
||||
Ratio of testing samples out of the whole dataset. Should be in (0.0, 1.0). And its sum with
|
||||
:obj:`valid_ratio` should be in (0.0, 1.0) as well. This parameter is invalid
|
||||
when :obj:`name` is :obj:`"ml-100k"`, since its testing samples are pre-specified.
|
||||
Default: None
|
||||
raw_dir : str, optional
|
||||
Raw file directory to download/store the data.
|
||||
Default: ~/.dgl/
|
||||
force_reload : bool, optional
|
||||
Whether to re-download(if the dataset has not been downloaded) and re-process the dataset.
|
||||
Default: False
|
||||
verbose : bool, optional
|
||||
Whether to print progress information. Default: True.
|
||||
transform : callable, optional
|
||||
A transform that takes in a :class:`~dgl.DGLGraph` object and returns
|
||||
a transformed version. The :class:`~dgl.DGLGraph` object will be
|
||||
transformed before every access.
|
||||
random_state : int, optional
|
||||
Random seed used for random dataset split. Default: 0
|
||||
|
||||
Notes
|
||||
-----
|
||||
- When :obj:`name` is :obj:`"ml-100k"`, the :obj:`test_ratio` is invalid, and the training ratio is equal to 1-:obj:`valid_ratio`.
|
||||
When :obj:`name` is :obj:`"ml-1m"` or :obj:`"ml-10m"`, the :obj:`test_ratio` is valid,
|
||||
and the training ratio is equal to 1-:obj:`valid_ratio`-:obj:`test_ratio`.
|
||||
- The number of edges is doubled to form an undirected(bidirected) graph structure.
|
||||
|
||||
Examples
|
||||
--------
|
||||
>>> from dgl.data import MovieLensDataset
|
||||
>>> dataset = MovieLensDataset(name='ml-100k', valid_ratio=0.2)
|
||||
>>> g = dataset[0]
|
||||
>>> g
|
||||
Graph(num_nodes={'movie': 1682, 'user': 943},
|
||||
num_edges={('movie', 'movie-user', 'user'): 100000, ('user', 'user-movie', 'movie'): 100000},
|
||||
metagraph=[('movie', 'user', 'movie-user'), ('user', 'movie', 'user-movie')])
|
||||
|
||||
>>> # get ratings of edges in the training graph.
|
||||
>>> rate = g.edges['user-movie'].data['rate'] # or rate = g.edges['movie-user'].data['rate']
|
||||
>>> rate
|
||||
tensor([5., 5., 3., ..., 3., 3., 5.])
|
||||
|
||||
>>> # get train, valid and test mask of edges
|
||||
>>> train_mask = g.edges['user-movie'].data['train_mask']
|
||||
>>> valid_mask = g.edges['user-movie'].data['valid_mask']
|
||||
>>> test_mask = g.edges['user-movie'].data['test_mask']
|
||||
|
||||
>>> # get train, valid and test ratings
|
||||
>>> train_ratings = rate[train_mask]
|
||||
>>> valid_ratings = rate[valid_mask]
|
||||
>>> test_ratings = rate[test_mask]
|
||||
|
||||
>>> # get input features of users
|
||||
>>> g.nodes["user"].data["feat"] # or g.nodes["movie"].data["feat"] for movie nodes
|
||||
tensor([[0.4800, 0.0000, 0.0000, ..., 0.0000, 0.0000, 0.0000],
|
||||
[1.0600, 1.0000, 0.0000, ..., 0.0000, 0.0000, 0.0000],
|
||||
[0.4600, 0.0000, 0.0000, ..., 0.0000, 0.0000, 0.0000],
|
||||
...,
|
||||
[0.4000, 0.0000, 1.0000, ..., 0.0000, 0.0000, 0.0000],
|
||||
[0.9600, 1.0000, 0.0000, ..., 0.0000, 0.0000, 0.0000],
|
||||
[0.4400, 0.0000, 1.0000, ..., 0.0000, 0.0000, 0.0000]])
|
||||
|
||||
"""
|
||||
|
||||
_url = {
|
||||
"ml-100k": "dataset/ml-100k.zip",
|
||||
"ml-1m": "dataset/ml-1m.zip",
|
||||
"ml-10m": "dataset/ml-10m.zip",
|
||||
}
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
name,
|
||||
valid_ratio,
|
||||
test_ratio=None,
|
||||
raw_dir=None,
|
||||
force_reload=None,
|
||||
verbose=None,
|
||||
transform=None,
|
||||
random_state=0,
|
||||
):
|
||||
check_pytorch()
|
||||
assert name in [
|
||||
"ml-100k",
|
||||
"ml-1m",
|
||||
"ml-10m",
|
||||
], f"currently movielens does not support {name}"
|
||||
|
||||
# test regarding valid and test split ratio
|
||||
assert (
|
||||
valid_ratio > 0.0 and valid_ratio < 1.0
|
||||
), f"valid_ratio {valid_ratio} must be in (0.0, 1.0)"
|
||||
|
||||
if name in ["ml-1m", "ml-10m"]:
|
||||
assert (
|
||||
test_ratio is not None and test_ratio > 0.0 and test_ratio < 1.0
|
||||
), f"test_ratio({test_ratio}) must be set to a value in (0.0, 1.0) when using ml-1m and ml-10m"
|
||||
assert (
|
||||
test_ratio + valid_ratio > 0.0
|
||||
and test_ratio + valid_ratio < 1.0
|
||||
), f"test_ratio({test_ratio}) + valid_ratio({valid_ratio}) must be set to (0.0, 1.0) when using ml-1m and ml-10m"
|
||||
|
||||
if name == "ml-100k" and test_ratio is not None:
|
||||
dgl_warning(
|
||||
f"test_ratio ({test_ratio}) is not set to None for ml-100k. "
|
||||
"Note that dataset split would not be affected by the test_ratio since "
|
||||
"testing samples of ml-100k have been pre-specified."
|
||||
)
|
||||
|
||||
self.valid_ratio = valid_ratio
|
||||
self.test_ratio = test_ratio
|
||||
self.random_state = random_state
|
||||
|
||||
if name == "ml-100k":
|
||||
self.genres = GENRES_ML_100K
|
||||
elif name == "ml-1m":
|
||||
self.genres = GENRES_ML_1M
|
||||
elif name == "ml-10m":
|
||||
self.genres = GENRES_ML_10M
|
||||
else:
|
||||
raise NotImplementedError
|
||||
|
||||
super(MovieLensDataset, self).__init__(
|
||||
name=name,
|
||||
url=_get_dgl_url(self._url[name]),
|
||||
raw_dir=raw_dir,
|
||||
force_reload=force_reload,
|
||||
verbose=verbose,
|
||||
transform=transform,
|
||||
)
|
||||
|
||||
def check_version(self):
|
||||
valid_ratio, test_ratio = load_info(self.version_path)
|
||||
if self.valid_ratio == valid_ratio and (
|
||||
self.test_ratio == test_ratio if self.name != "ml-100k" else True
|
||||
):
|
||||
return True
|
||||
else:
|
||||
if self.name == "ml-100k":
|
||||
print(
|
||||
f"The current valid ratio ({self.valid_ratio}) "
|
||||
"is not the same as the last setting "
|
||||
f"(valid: {valid_ratio}). "
|
||||
f"MovieLens {self.name} will be re-processed with the new dataset split setting."
|
||||
)
|
||||
else:
|
||||
print(
|
||||
f"At least one of current valid ({self.valid_ratio}) and test ({self.test_ratio}) ratio "
|
||||
"are not the same as the last setting "
|
||||
f"(valid: {valid_ratio}, test: {test_ratio}). "
|
||||
f"MovieLens {self.name} will be re-processed with the new dataset split setting."
|
||||
)
|
||||
return False
|
||||
|
||||
def download(self):
|
||||
zip_file_path = os.path.join(self.raw_dir, self.name + ".zip")
|
||||
download(self.url, path=zip_file_path)
|
||||
extract_archive(zip_file_path, self.raw_dir, overwrite=True)
|
||||
|
||||
def process(self):
|
||||
print(f"Starting processing {self.name} ...")
|
||||
|
||||
# 0. loading movie features
|
||||
movie_feat = load_info(
|
||||
os.path.join(self.raw_path, "movie_feat.pkl")
|
||||
).to(torch.float)
|
||||
# 1. dataset split: train + (valid + ) test
|
||||
if self.name == "ml-100k":
|
||||
train_rating_data = self._load_raw_rates(
|
||||
os.path.join(self.raw_path, "u1.base"), "\t"
|
||||
)
|
||||
test_rating_data = self._load_raw_rates(
|
||||
os.path.join(self.raw_path, "u1.test"), "\t"
|
||||
)
|
||||
indices = np.arange(len(train_rating_data))
|
||||
train, valid, _ = split_dataset(
|
||||
indices,
|
||||
[1 - self.valid_ratio, self.valid_ratio, 0.0],
|
||||
shuffle=True,
|
||||
random_state=self.random_state,
|
||||
)
|
||||
train_rating_data, valid_rating_data = (
|
||||
train_rating_data.iloc[train.indices],
|
||||
train_rating_data.iloc[valid.indices],
|
||||
)
|
||||
all_rating_data = pd.concat(
|
||||
[train_rating_data, valid_rating_data, test_rating_data]
|
||||
)
|
||||
|
||||
elif self.name == "ml-1m" or self.name == "ml-10m":
|
||||
all_rating_data = self._load_raw_rates(
|
||||
os.path.join(self.raw_path, "ratings.dat"), "::"
|
||||
)
|
||||
indices = np.arange(len(all_rating_data))
|
||||
train, valid, test = split_dataset(
|
||||
indices,
|
||||
[
|
||||
1 - self.valid_ratio - self.test_ratio,
|
||||
self.valid_ratio,
|
||||
self.test_ratio,
|
||||
],
|
||||
shuffle=True,
|
||||
random_state=self.random_state,
|
||||
)
|
||||
train_rating_data, valid_rating_data, test_rating_data = (
|
||||
all_rating_data.iloc[train.indices],
|
||||
all_rating_data.iloc[valid.indices],
|
||||
all_rating_data.iloc[test.indices],
|
||||
)
|
||||
|
||||
# 2. load user and movie data, and drop those unseen in rating_data
|
||||
user_data = self._load_raw_user_data()
|
||||
movie_data = self._load_raw_movie_data()
|
||||
user_data = self._drop_unseen_nodes(
|
||||
data_df=user_data,
|
||||
col_name="id",
|
||||
reserved_ids_set=set(all_rating_data["user_id"].values),
|
||||
)
|
||||
movie_data = self._drop_unseen_nodes(
|
||||
data_df=movie_data,
|
||||
col_name="id",
|
||||
reserved_ids_set=set(all_rating_data["movie_id"].values),
|
||||
)
|
||||
|
||||
user_feat = Tensor(self._process_user_feat(user_data))
|
||||
|
||||
# 3. generate rating pairs
|
||||
# Map user/movie to the global id
|
||||
self._global_user_id_map = {
|
||||
ele: i for i, ele in enumerate(user_data["id"])
|
||||
}
|
||||
self._global_movie_id_map = {
|
||||
ele: i for i, ele in enumerate(movie_data["id"])
|
||||
}
|
||||
|
||||
# pair value is idx rather than id
|
||||
u_indices, v_indices, labels = self._generate_pair_value(
|
||||
all_rating_data
|
||||
)
|
||||
all_rating_pairs = (
|
||||
LongTensor(u_indices),
|
||||
LongTensor(v_indices),
|
||||
)
|
||||
all_rating_values = Tensor(labels)
|
||||
|
||||
graph = self.construct_g(
|
||||
all_rating_pairs, all_rating_values, user_feat, movie_feat
|
||||
)
|
||||
self.graph = self.add_masks(
|
||||
graph, train_rating_data, valid_rating_data, test_rating_data
|
||||
)
|
||||
|
||||
print(f"End processing {self.name} ...")
|
||||
|
||||
def construct_g(self, rate_pairs, rate_values, user_feat, movie_feat):
|
||||
g = heterograph(
|
||||
{
|
||||
("user", "user-movie", "movie"): (rate_pairs[0], rate_pairs[1]),
|
||||
("movie", "movie-user", "user"): (rate_pairs[1], rate_pairs[0]),
|
||||
}
|
||||
)
|
||||
ndata = {"user": user_feat, "movie": movie_feat}
|
||||
edata = {"user-movie": rate_values, "movie-user": rate_values}
|
||||
g.ndata["feat"] = ndata
|
||||
g.edata["rate"] = edata
|
||||
return g
|
||||
|
||||
def add_masks(
|
||||
self, g, train_rating_data, valid_rating_data, test_rating_data
|
||||
):
|
||||
train_u_indices, train_v_indices, _ = self._generate_pair_value(
|
||||
train_rating_data
|
||||
)
|
||||
valid_u_indices, valid_v_indices, _ = self._generate_pair_value(
|
||||
valid_rating_data
|
||||
)
|
||||
test_u_indices, test_v_indices, _ = self._generate_pair_value(
|
||||
test_rating_data
|
||||
)
|
||||
|
||||
# user-movie
|
||||
train_mask = torch.zeros((g.num_edges("user-movie"),), dtype=torch.bool)
|
||||
train_mask[
|
||||
g.edge_ids(train_u_indices, train_v_indices, etype="user-movie")
|
||||
] = True
|
||||
valid_mask = torch.zeros((g.num_edges("user-movie"),), dtype=torch.bool)
|
||||
valid_mask[
|
||||
g.edge_ids(valid_u_indices, valid_v_indices, etype="user-movie")
|
||||
] = True
|
||||
test_mask = torch.zeros((g.num_edges("user-movie"),), dtype=torch.bool)
|
||||
test_mask[
|
||||
g.edge_ids(test_u_indices, test_v_indices, etype="user-movie")
|
||||
] = True
|
||||
|
||||
g.edges["user-movie"].data["train_mask"] = train_mask
|
||||
g.edges["user-movie"].data["valid_mask"] = valid_mask
|
||||
g.edges["user-movie"].data["test_mask"] = test_mask
|
||||
|
||||
# movie-user
|
||||
train_mask_rev = torch.zeros(
|
||||
(g.num_edges("movie-user"),), dtype=torch.bool
|
||||
)
|
||||
train_mask_rev[
|
||||
g.edge_ids(train_v_indices, train_u_indices, etype="movie-user")
|
||||
] = True
|
||||
valid_mask_rev = torch.zeros(
|
||||
(g.num_edges("movie-user"),), dtype=torch.bool
|
||||
)
|
||||
valid_mask_rev[
|
||||
g.edge_ids(valid_v_indices, valid_u_indices, etype="movie-user")
|
||||
] = True
|
||||
test_mask_rev = torch.zeros(
|
||||
(g.num_edges("movie-user"),), dtype=torch.bool
|
||||
)
|
||||
test_mask_rev[
|
||||
g.edge_ids(test_v_indices, test_u_indices, etype="movie-user")
|
||||
] = True
|
||||
|
||||
g.edges["movie-user"].data["train_mask"] = train_mask_rev
|
||||
g.edges["movie-user"].data["valid_mask"] = valid_mask_rev
|
||||
g.edges["movie-user"].data["test_mask"] = test_mask_rev
|
||||
|
||||
return g
|
||||
|
||||
def has_cache(self):
|
||||
if (
|
||||
os.path.exists(self.graph_path)
|
||||
and os.path.exists(self.version_path)
|
||||
and self.check_version()
|
||||
):
|
||||
return True
|
||||
return False
|
||||
|
||||
def save(self):
|
||||
save_graphs(self.graph_path, [self.graph])
|
||||
save_info(self.version_path, [self.valid_ratio, self.test_ratio])
|
||||
if self.verbose:
|
||||
print(f"Done saving data into {self.raw_path}.")
|
||||
|
||||
def load(self):
|
||||
g_list, _ = load_graphs(self.graph_path)
|
||||
self.graph = g_list[0]
|
||||
|
||||
"""
|
||||
To avoid the problem each time loading boolean tensor from the disk, boolean values
|
||||
would be automatically converted into torch.uint8 types, and a deprecation warning would
|
||||
be raised for using torch.uint8
|
||||
"""
|
||||
for e in self.graph.etypes:
|
||||
self.graph.edges[e].data["train_mask"] = (
|
||||
self.graph.edges[e].data["train_mask"].to(torch.bool)
|
||||
)
|
||||
self.graph.edges[e].data["valid_mask"] = (
|
||||
self.graph.edges[e].data["valid_mask"].to(torch.bool)
|
||||
)
|
||||
self.graph.edges[e].data["test_mask"] = (
|
||||
self.graph.edges[e].data["test_mask"].to(torch.bool)
|
||||
)
|
||||
|
||||
def __getitem__(self, idx):
|
||||
assert (
|
||||
idx == 0
|
||||
), "This dataset has only one set of training, validation and testing graph"
|
||||
if self._transform is None:
|
||||
return self.graph
|
||||
else:
|
||||
return self._transform(self.graph)
|
||||
|
||||
def __len__(self):
|
||||
return 1
|
||||
|
||||
@property
|
||||
def raw_path(self):
|
||||
return os.path.join(self.raw_dir, self.name)
|
||||
|
||||
@property
|
||||
def graph_path(self):
|
||||
return os.path.join(self.raw_path, self.name + ".bin")
|
||||
|
||||
@property
|
||||
def version_path(self):
|
||||
return os.path.join(self.raw_path, self.name + "_version.pkl")
|
||||
|
||||
def _process_user_feat(self, user_data):
|
||||
if self.name == "ml-100k" or self.name == "ml-1m":
|
||||
ages = user_data["age"].values.astype(np.float32)
|
||||
gender = (user_data["gender"] == "F").values.astype(np.float32)
|
||||
all_occupations = set(user_data["occupation"])
|
||||
occupation_map = {ele: i for i, ele in enumerate(all_occupations)}
|
||||
occupation_one_hot = np.zeros(
|
||||
shape=(user_data.shape[0], len(all_occupations)),
|
||||
dtype=np.float32,
|
||||
)
|
||||
occupation_one_hot[
|
||||
np.arange(user_data.shape[0]),
|
||||
np.array(
|
||||
[occupation_map[ele] for ele in user_data["occupation"]]
|
||||
),
|
||||
] = 1
|
||||
user_features = np.concatenate(
|
||||
[
|
||||
ages.reshape((user_data.shape[0], 1)) / 50.0,
|
||||
gender.reshape((user_data.shape[0], 1)),
|
||||
occupation_one_hot,
|
||||
],
|
||||
axis=1,
|
||||
)
|
||||
elif self.name == "ml-10m":
|
||||
user_features = np.zeros(
|
||||
shape=(user_data.shape[0], 1), dtype=np.float32
|
||||
)
|
||||
else:
|
||||
raise NotImplementedError
|
||||
return user_features
|
||||
|
||||
def _load_raw_user_data(self):
|
||||
if self.name == "ml-100k":
|
||||
user_data = pd.read_csv(
|
||||
os.path.join(self.raw_path, "u.user"),
|
||||
sep="|",
|
||||
header=None,
|
||||
names=["id", "age", "gender", "occupation", "zip_code"],
|
||||
engine="python",
|
||||
)
|
||||
elif self.name == "ml-1m":
|
||||
user_data = pd.read_csv(
|
||||
os.path.join(self.raw_path, "users.dat"),
|
||||
sep="::",
|
||||
header=None,
|
||||
names=["id", "gender", "age", "occupation", "zip_code"],
|
||||
engine="python",
|
||||
)
|
||||
elif self.name == "ml-10m":
|
||||
rating_info = pd.read_csv(
|
||||
os.path.join(self.raw_path, "ratings.dat"),
|
||||
sep="::",
|
||||
header=None,
|
||||
names=["user_id", "movie_id", "rating", "timestamp"],
|
||||
dtype={
|
||||
"user_id": np.int32,
|
||||
"movie_id": np.int32,
|
||||
"ratings": np.float32,
|
||||
"timestamp": np.int64,
|
||||
},
|
||||
engine="python",
|
||||
)
|
||||
user_data = pd.DataFrame(
|
||||
np.unique(rating_info["user_id"].values.astype(np.int32)),
|
||||
columns=["id"],
|
||||
)
|
||||
else:
|
||||
raise NotImplementedError
|
||||
return user_data
|
||||
|
||||
def _load_raw_movie_data(self):
|
||||
file_path = os.path.join(self.raw_path, "u.item")
|
||||
if self.name == "ml-100k":
|
||||
movie_data = pd.read_csv(
|
||||
file_path,
|
||||
sep="|",
|
||||
header=None,
|
||||
names=[
|
||||
"id",
|
||||
"title",
|
||||
"release_date",
|
||||
"video_release_date",
|
||||
"url",
|
||||
]
|
||||
+ GENRES_ML_100K,
|
||||
engine="python",
|
||||
encoding="ISO-8859-1",
|
||||
)
|
||||
elif self.name == "ml-1m" or self.name == "ml-10m":
|
||||
file_path = os.path.join(self.raw_path, "movies.dat")
|
||||
movie_data = pd.read_csv(
|
||||
file_path,
|
||||
sep="::",
|
||||
header=None,
|
||||
names=["id", "title", "genres"],
|
||||
encoding="iso-8859-1",
|
||||
engine="python",
|
||||
)
|
||||
genre_map = {ele: i for i, ele in enumerate(self.genres)}
|
||||
genre_map["Children's"] = genre_map["Children"]
|
||||
genre_map["Childrens"] = genre_map["Children"]
|
||||
movie_genres = np.zeros(
|
||||
shape=(movie_data.shape[0], len(self.genres)), dtype=np.float32
|
||||
)
|
||||
for i, genres in enumerate(movie_data["genres"]):
|
||||
for ele in genres.split("|"):
|
||||
if ele in genre_map:
|
||||
movie_genres[i, genre_map[ele]] = 1.0
|
||||
else:
|
||||
movie_genres[i, genre_map["unknown"]] = 1.0
|
||||
for idx, genre_name in enumerate(self.genres):
|
||||
movie_data[genre_name] = movie_genres[:, idx]
|
||||
movie_data = movie_data.drop(columns=["genres"])
|
||||
else:
|
||||
raise NotImplementedError
|
||||
|
||||
return movie_data
|
||||
|
||||
def _load_raw_rates(self, file_path, sep):
|
||||
rating_data = pd.read_csv(
|
||||
file_path,
|
||||
sep=sep,
|
||||
header=None,
|
||||
names=["user_id", "movie_id", "rating", "timestamp"],
|
||||
dtype={
|
||||
"user_id": np.int32,
|
||||
"movie_id": np.int32,
|
||||
"ratings": np.float32,
|
||||
"timestamp": np.int64,
|
||||
},
|
||||
engine="python",
|
||||
)
|
||||
rating_data = rating_data.reset_index(drop=True)
|
||||
return rating_data
|
||||
|
||||
def _drop_unseen_nodes(self, data_df, col_name, reserved_ids_set):
|
||||
data_df = data_df[data_df[col_name].isin(reserved_ids_set)]
|
||||
data_df.reset_index(drop=True, inplace=True)
|
||||
return data_df
|
||||
|
||||
def _generate_pair_value(self, rating_data):
|
||||
rating_pairs = (
|
||||
np.array(
|
||||
[
|
||||
self._global_user_id_map[ele]
|
||||
for ele in rating_data["user_id"]
|
||||
],
|
||||
dtype=np.int32,
|
||||
),
|
||||
np.array(
|
||||
[
|
||||
self._global_movie_id_map[ele]
|
||||
for ele in rating_data["movie_id"]
|
||||
],
|
||||
dtype=np.int32,
|
||||
),
|
||||
)
|
||||
rating_values = rating_data["rating"].values.astype(np.float32)
|
||||
return rating_pairs[0], rating_pairs[1], rating_values
|
||||
|
||||
def __repr__(self):
|
||||
return (
|
||||
f'Dataset("{self.name}", num_graphs={len(self)},'
|
||||
+ f" save_path={self.raw_path}), valid_ratio={self.valid_ratio}, test_ratio={self.test_ratio}"
|
||||
)
|
||||
Reference in New Issue
Block a user