{"metadata":{"kernelspec":{"language":"python","display_name":"Python 3","name":"python3"},"language_info":{"name":"python","version":"3.10.13","mimetype":"text/x-python","codemirror_mode":{"name":"ipython","version":3},"pygments_lexer":"ipython3","nbconvert_exporter":"python","file_extension":".py"},"kaggle":{"accelerator":"none","dataSources":[{"sourceId":36171,"databundleVersionId":3543906,"sourceType":"competition"}],"dockerImageVersionId":30746,"isInternetEnabled":true,"language":"python","sourceType":"notebook","isGpuEnabled":false}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"code","source":"# This Python 3 environment comes with many helpful analytics libraries installed\n# It is defined by the kaggle/python Docker image: https://github.com/kaggle/docker-python\n# For example, here's several helpful packages to load\n\nimport numpy as np # linear algebra\nimport pandas as pd # data processing, CSV file I/O (e.g. pd.read_csv)\n\n# Input data files are available in the read-only \"../input/\" directory\n# For example, running this (by clicking run or pressing Shift+Enter) will list all files under the input directory\n\nimport os\nfor dirname, _, filenames in os.walk('/kaggle/input'):\n    for filename in filenames:\n        print(os.path.join(dirname, filename))\n\n# You can write up to 20GB to the current directory (/kaggle/working/) that gets preserved as output when you create a version using \"Save & Run All\" \n# You can also write temporary files to /kaggle/temp/, but they won't be saved outside of the current session","metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","execution":{"iopub.status.busy":"2024-08-04T09:34:55.562116Z","iopub.execute_input":"2024-08-04T09:34:55.562833Z","iopub.status.idle":"2024-08-04T09:34:57.198629Z","shell.execute_reply.started":"2024-08-04T09:34:55.56271Z","shell.execute_reply":"2024-08-04T09:34:57.196749Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# Import necessary libraries\nfrom fastai.collab import *\nfrom fastai.tabular.all import *\nimport pandas as pd\nimport torch\nimport matplotlib.pyplot as plt\nimport os\nimport gc\n\n# Set seed for reproducibility\nset_seed(42)\n\n# Load Kaggle data\nfor dirname, _, filenames in os.walk('/kaggle/input/anime-recommendation-system-by-tah'):\n    for filename in filenames:\n        print(os.path.join(dirname, filename))\n\n# Read data\nratings = pd.read_csv('/kaggle/input/anime-recommendation-system-by-tah/rating_complete.csv')\nanime = pd.read_csv('/kaggle/input/anime-recommendation-system-by-tah/anime.csv')\n\n# Check columns to understand the structure\nprint(\"Ratings DataFrame columns: \", ratings.columns)\nprint(\"Anime DataFrame columns: \", anime.columns)\n\n# Inspect the first few rows to understand the structure better\nprint(\"Ratings DataFrame sample: \\n\", ratings.head())\nprint(\"Anime DataFrame sample: \\n\", anime.head())\n\n# Ensure column names are consistent for merging\n# Rename 'MAL_ID' to 'anime_id' in the anime DataFrame for merging\nanime.rename(columns={'MAL_ID': 'anime_id', 'Name': 'anime_name'}, inplace=True)\nratings.rename(columns={'rating': 'user_rating'}, inplace=True)\n\n# Merge datasets\nratings = ratings.merge(anime, on='anime_id')\n\n# Optimize data types to save memory\nratings['user_id'] = ratings['user_id'].astype('int32')\nratings['anime_id'] = ratings['anime_id'].astype('int32')\nratings['user_rating'] = ratings['user_rating'].astype('float32')\n\n# Check the merged DataFrame\nprint(\"Merged DataFrame columns: \", ratings.columns)\nprint(\"Merged DataFrame sample: \\n\", ratings.head())\n\n# Create DataLoaders with a smaller batch size\ndls = CollabDataLoaders.from_df(ratings, user_name='user_id', item_name='anime_name', rating_name='user_rating', bs=32)\n\n# Define model parameters\nn_users = len(dls.classes['user_id'])\nn_items = len(dls.classes['anime_name'])\nn_factors = 50\n\n# Define create_params function\ndef create_params(size):\n    return nn.Parameter(torch.zeros(*size).normal_(0, 0.01))\n\n# Create custom DotProductBias model\nclass DotProductBias(Module):\n    def __init__(self, n_users, n_items, n_factors, y_range=(0, 5.5)):\n        self.user_factors = create_params([n_users, n_factors])\n        self.user_bias = create_params([n_users])\n        self.item_factors = create_params([n_items, n_factors])\n        self.item_bias = create_params([n_items])\n        self.y_range = y_range\n        \n    def forward(self, x):\n        users = self.user_factors[x[:, 0]]\n        items = self.item_factors[x[:, 1]]\n        res = (users * items).sum(dim=1)\n        res += self.user_bias[x[:, 0]] + self.item_bias[x[:, 1]]\n        return sigmoid_range(res, *self.y_range)\n\n# Initialize model\nmodel = DotProductBias(n_users, n_items, n_factors=n_factors)\nlearn = Learner(dls, model, loss_func=MSELossFlat())\n\n# Train model with reduced epochs and smaller batch size\nlearn.fit_one_cycle(3, 5e-3, wd=0.1)\n\n# Clear unused variables and perform garbage collection\ndel ratings, anime, dls, model\ngc.collect()\n\n# Define CollabNN model\nclass CollabNN(Module):\n    def __init__(self, user_sz, item_sz, y_range=(0, 5.5), n_act=100):\n        self.user_factors = Embedding(*user_sz)\n        self.item_factors = Embedding(*item_sz)\n        self.layers = nn.Sequential(\n            nn.Linear(user_sz[1] + item_sz[1], n_act),\n            nn.ReLU(),\n            nn.Linear(n_act, 1)\n        )\n        self.y_range = y_range\n        \n    def forward(self, x):\n        embs = self.user_factors(x[:, 0]), self.item_factors(x[:, 1])\n        x = self.layers(torch.cat(embs, dim=1))\n        return sigmoid_range(x, *self.y_range)\n\n# Reload DataLoaders with smaller batch size\nratings = pd.read_csv('/kaggle/input/anime-recommendation-system-by-tah/rating_complete.csv')\nratings = ratings.merge(anime, on='anime_id')\ndls = CollabDataLoaders.from_df(ratings, user_name='user_id', item_name='anime_name', rating_name='user_rating', bs=32)\n\n# Create embeddings\nembs = get_emb_sz(dls)\n\n# Initialize and train CollabNN model with smaller batch size\nmodel = CollabNN(*embs)\nlearn = Learner(dls, model, loss_func=MSELossFlat())\nlearn.fit_one_cycle(3, 5e-3, wd=0.01)\n\n# Optionally, use fastai's built-in collaborative filtering learner with neural network\nlearn = collab_learner(dls, use_nn=True, y_range=(0, 5.5), layers=[100, 50])\nlearn.fit_one_cycle(3, 5e-3, wd=0.1)\n\n# Generate predictions\npreds, _ = learn.get_preds()\npredictions = pd.DataFrame({'user_id': dls.classes['user_id'][dls.train_ds.items['user_id']],\n                            'anime_id': dls.classes['anime_name'][dls.train_ds.items['anime_name']],\n                            'rating': preds.numpy().flatten()})\n\n# Save predictions for Kaggle submission\npredictions.to_csv('submission.csv', index=False)\n\n# Check if the file is saved\nprint(\"Saved files in the working directory:\")\nfor file in os.listdir('/kaggle/working/'):\n    print(file)\n\n# Visualize item embeddings\ng = ratings.groupby('anime_name')['user_rating'].count()\ntop_items = g.sort_values(ascending=False).index.values[:1000]\ntop_idxs = tensor([learn.dls.classes['anime_name'].o2i[m] for m in top_items])\nitem_w = learn.model.i_weight.weight[top_idxs].cpu().detach()\nitem_pca = item_w.pca(3)\nfac0, fac1, fac2 = item_pca.t()\nidxs = list(range(50))\nX = fac0[idxs]\nY = fac2[idxs]\nplt.figure(figsize=(12, 12))\nplt.scatter(X, Y)\nfor i, x, y in zip(top_items[idxs], X, Y):\n    plt.text(x, y, i, color=np.random.rand(3) * 0.7, fontsize=11)\nplt.show()\n","metadata":{"execution":{"iopub.status.busy":"2024-08-04T12:39:38.353877Z","iopub.execute_input":"2024-08-04T12:39:38.354334Z"},"trusted":true},"execution_count":null,"outputs":[]}]}