ZeroShirayuki commited on
Commit
6a3d3b0
·
verified ·
1 Parent(s): 677ca77

Upload 4 files

Browse files
Files changed (4) hide show
  1. README.md +11 -0
  2. app.py +81 -0
  3. requirements.txt +7 -0
  4. train.py +111 -0
README.md ADDED
@@ -0,0 +1,11 @@
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Manga Recommender Model
2
+
3
+ A matrix factorization model for manga recommendations.
4
+
5
+ ## API Endpoints
6
+
7
+ ### GET /predict
8
+ Get recommendations for a user
9
+
10
+ ### POST /update
11
+ Update the model with new ratings
app.py ADDED
@@ -0,0 +1,81 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # app.py
2
+ from transformers import pipeline
3
+ from fastapi import FastAPI, HTTPException
4
+ import torch
5
+ from typing import List, Dict
6
+ import json
7
+
8
+ app = FastAPI()
9
+
10
+ # Load model and mappings
11
+ checkpoint = torch.load('manga_recommender.pt')
12
+ model = MangaRecommender(
13
+ num_users=len(checkpoint['user_mapping']),
14
+ num_items=len(checkpoint['manga_mapping'])
15
+ )
16
+ model.load_state_dict(checkpoint['model_state_dict'])
17
+ user_mapping = checkpoint['user_mapping']
18
+ manga_mapping = checkpoint['manga_mapping']
19
+ reverse_manga_mapping = {v: k for k, v in manga_mapping.items()}
20
+
21
+ @app.post("/predict")
22
+ async def predict(user_id: str, top_k: int = 10):
23
+ try:
24
+ # Get user index
25
+ user_idx = user_mapping.get(user_id)
26
+ if user_idx is None:
27
+ # Handle cold start
28
+ return {"error": "User not found"}
29
+
30
+ # Get predictions
31
+ model.eval()
32
+ with torch.no_grad():
33
+ user_tensor = torch.tensor([user_idx])
34
+ predictions = model.predict(user_tensor)
35
+ scores, indices = torch.topk(predictions[0], k=top_k)
36
+
37
+ # Convert back to manga IDs
38
+ manga_ids = [reverse_manga_mapping[idx.item()] for idx in indices]
39
+ scores = scores.tolist()
40
+
41
+ return {
42
+ "manga_ids": manga_ids,
43
+ "scores": scores
44
+ }
45
+ except Exception as e:
46
+ raise HTTPException(status_code=500, detail=str(e))
47
+
48
+ @app.post("/update")
49
+ async def update_model(ratings: List[Dict]):
50
+ try:
51
+ # Convert ratings to training format
52
+ df = pd.DataFrame(ratings)
53
+ df['user_idx'] = df['user_id'].map(user_mapping)
54
+ df['manga_idx'] = df['manga_id'].map(manga_mapping)
55
+
56
+ # Create dataset
57
+ dataset = MangaDataset(df)
58
+ loader = DataLoader(dataset, batch_size=64)
59
+
60
+ # Update model
61
+ optimizer = torch.optim.Adam(model.parameters(), lr=0.001)
62
+ criterion = nn.MSELoss()
63
+
64
+ model.train()
65
+ for user, item, rating in loader:
66
+ optimizer.zero_grad()
67
+ pred = model(user, item)
68
+ loss = criterion(pred, rating)
69
+ loss.backward()
70
+ optimizer.step()
71
+
72
+ # Save updated model
73
+ torch.save({
74
+ 'model_state_dict': model.state_dict(),
75
+ 'user_mapping': user_mapping,
76
+ 'manga_mapping': manga_mapping
77
+ }, 'manga_recommender.pt')
78
+
79
+ return {"message": "Model updated successfully"}
80
+ except Exception as e:
81
+ raise HTTPException(status_code=500, detail=str(e))
requirements.txt ADDED
@@ -0,0 +1,7 @@
 
 
 
 
 
 
 
 
1
+ torch
2
+ fastapi
3
+ uvicorn
4
+ pandas
5
+ numpy
6
+ scikit-learn
7
+ transformers
train.py ADDED
@@ -0,0 +1,111 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # train.py
2
+ import torch
3
+ import pandas as pd
4
+ import numpy as np
5
+ from torch import nn
6
+ from torch.utils.data import Dataset, DataLoader
7
+ from sklearn.model_selection import train_test_split
8
+
9
+ class MangaDataset(Dataset):
10
+ def __init__(self, ratings_df):
11
+ self.users = torch.tensor(ratings_df['user_idx'].values, dtype=torch.long)
12
+ self.items = torch.tensor(ratings_df['manga_idx'].values, dtype=torch.long)
13
+ self.ratings = torch.tensor(ratings_df['rating'].values, dtype=torch.float)
14
+
15
+ def __len__(self):
16
+ return len(self.ratings)
17
+
18
+ def __getitem__(self, idx):
19
+ return self.users[idx], self.items[idx], self.ratings[idx]
20
+
21
+ class MangaRecommender(nn.Module):
22
+ def __init__(self, num_users, num_items, n_factors=50):
23
+ super().__init__()
24
+ self.user_factors = nn.Embedding(num_users, n_factors)
25
+ self.item_factors = nn.Embedding(num_items, n_factors)
26
+
27
+ # Initialize embeddings
28
+ nn.init.xavier_normal_(self.user_factors.weight)
29
+ nn.init.xavier_normal_(self.item_factors.weight)
30
+
31
+ def forward(self, user, item):
32
+ user_emb = self.user_factors(user)
33
+ item_emb = self.item_factors(item)
34
+ return (user_emb * item_emb).sum(1)
35
+
36
+ def predict(self, user_ids):
37
+ user_emb = self.user_factors(user_ids)
38
+ all_items = self.item_factors.weight
39
+ return torch.matmul(user_emb, all_items.t())
40
+
41
+ def train_model():
42
+ # Load your data
43
+ df = pd.read_csv('manga_ratings.csv')
44
+
45
+ # Create user and item mappings
46
+ user_mapping = {uid: idx for idx, uid in enumerate(df['user_id'].unique())}
47
+ manga_mapping = {mid: idx for idx, mid in enumerate(df['manga_id'].unique())}
48
+
49
+ # Convert ratings to numerical values
50
+ rating_map = {'like': 1.0, 'dislike': -1.0, None: 0.0}
51
+
52
+ # Prepare training data
53
+ df['user_idx'] = df['user_id'].map(user_mapping)
54
+ df['manga_idx'] = df['manga_id'].map(manga_mapping)
55
+ df['rating'] = df['like_status'].map(rating_map)
56
+
57
+ # Create train/val split
58
+ train_df, val_df = train_test_split(df, test_size=0.2)
59
+
60
+ # Create datasets
61
+ train_dataset = MangaDataset(train_df)
62
+ val_dataset = MangaDataset(val_df)
63
+
64
+ # Create dataloaders
65
+ train_loader = DataLoader(train_dataset, batch_size=64, shuffle=True)
66
+ val_loader = DataLoader(val_dataset, batch_size=64)
67
+
68
+ # Initialize model
69
+ model = MangaRecommender(
70
+ num_users=len(user_mapping),
71
+ num_items=len(manga_mapping)
72
+ )
73
+
74
+ # Training setup
75
+ criterion = nn.MSELoss()
76
+ optimizer = torch.optim.Adam(model.parameters(), lr=0.001)
77
+
78
+ # Training loop
79
+ num_epochs = 20
80
+ for epoch in range(num_epochs):
81
+ model.train()
82
+ total_loss = 0
83
+ for user, item, rating in train_loader:
84
+ optimizer.zero_grad()
85
+ pred = model(user, item)
86
+ loss = criterion(pred, rating)
87
+ loss.backward()
88
+ optimizer.step()
89
+ total_loss += loss.item()
90
+
91
+ # Validation
92
+ model.eval()
93
+ val_loss = 0
94
+ with torch.no_grad():
95
+ for user, item, rating in val_loader:
96
+ pred = model(user, item)
97
+ val_loss += criterion(pred, rating).item()
98
+
99
+ print(f'Epoch {epoch+1}/{num_epochs}')
100
+ print(f'Train Loss: {total_loss/len(train_loader):.4f}')
101
+ print(f'Val Loss: {val_loss/len(val_loader):.4f}')
102
+
103
+ # Save mappings and model
104
+ torch.save({
105
+ 'model_state_dict': model.state_dict(),
106
+ 'user_mapping': user_mapping,
107
+ 'manga_mapping': manga_mapping
108
+ }, 'manga_recommender.pt')
109
+
110
+ if __name__ == '__main__':
111
+ train_model()