Upload folder using huggingface_hub
Browse files- .gitattributes +3 -0
- MANIFEST.sha256 +14 -0
- README.md +108 -0
- akas_es.json +0 -0
- catalog.sqlite +3 -0
- feature_vocabs.json +0 -0
- full_aux.npz +3 -0
- full_vocab_map.json +0 -0
- item_embeddings.npy +3 -0
- item_embeddings_full.npy +3 -0
- meta.json +10 -0
- meta_full.json +10 -0
- model_fp32.onnx +3 -0
- model_fp32.onnx.data +3 -0
- model_full_fp32.onnx +3 -0
- model_full_fp32.onnx.data +3 -0
- scripts/build_catalog.py +107 -0
- vocab_map.json +0 -0
.gitattributes
CHANGED
|
@@ -33,3 +33,6 @@ saved_model/**/* filter=lfs diff=lfs merge=lfs -text
|
|
| 33 |
*.zip filter=lfs diff=lfs merge=lfs -text
|
| 34 |
*.zst filter=lfs diff=lfs merge=lfs -text
|
| 35 |
*tfevents* filter=lfs diff=lfs merge=lfs -text
|
|
|
|
|
|
|
|
|
|
|
|
| 33 |
*.zip filter=lfs diff=lfs merge=lfs -text
|
| 34 |
*.zst filter=lfs diff=lfs merge=lfs -text
|
| 35 |
*tfevents* filter=lfs diff=lfs merge=lfs -text
|
| 36 |
+
catalog.sqlite filter=lfs diff=lfs merge=lfs -text
|
| 37 |
+
model_fp32.onnx.data filter=lfs diff=lfs merge=lfs -text
|
| 38 |
+
model_full_fp32.onnx.data filter=lfs diff=lfs merge=lfs -text
|
MANIFEST.sha256
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
6499646c6fdf7b6d5d88fd1c7ed0953b65632e2eedec65e9a518b2f30227a823 catalog.sqlite
|
| 2 |
+
af32b78680e044cd8f65513005d588e40ec50a212a0d2682e18611f8f97f0734 feature_vocabs.json
|
| 3 |
+
86f9c130291b6fb799685961b5b6691f95f81547fded1a917df824746c3f728f full_aux.npz
|
| 4 |
+
1940e1f1a495a209222f759c51587a4a6f0b56171de237bbc7bb84c405eb39d2 full_vocab_map.json
|
| 5 |
+
8013974fe435247132c5bc448f38c76d0eb9284511a6787b7d0d8fea0b0014ed item_embeddings_full.npy
|
| 6 |
+
4e00cdcbe6b633c4cbcc17d7179243bc5c3f106e281498109c7941efe7d06356 item_embeddings.npy
|
| 7 |
+
c1a5e720d7f5c77e6580346e77a38d5d74bb30da03d8ae3c835a583d6e7bd2a2 meta_full.json
|
| 8 |
+
665e05667124c17d207a819000ac29c08fae534d8f4eae0b734bc5cb015568a6 meta.json
|
| 9 |
+
5f605b98d542f94028999689aad4a70601f525b212ba102a7497aee909f24b2b model_fp32.onnx
|
| 10 |
+
f5449fa7a58fe2f3dbd3dc4562d51e9ce3299450f69a2903e75aee0615934cf6 model_fp32.onnx.data
|
| 11 |
+
a6f98f311a0d50ca20532ab3b15b482362c8f39ceebd149c57342831c62b1c07 model_full_fp32.onnx
|
| 12 |
+
6e3bd12e8c6466057d8c3fb09f8b4880a7229d30e4156e993ec310624ea4587f model_full_fp32.onnx.data
|
| 13 |
+
65df51b90355b7ea9af878ce6aed16a9a6fcdbe98f12bde71b4189aac3e58e53 vocab_map.json
|
| 14 |
+
445db02f9855f26ef4672089bce54ab74141c587306b635ad8a5e0fa961b73d5 scripts/build_catalog.py
|
README.md
ADDED
|
@@ -0,0 +1,108 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
---
|
| 2 |
+
license: cc-by-nc-sa-4.0
|
| 3 |
+
tags:
|
| 4 |
+
- recommendation
|
| 5 |
+
- sequential-recommendation
|
| 6 |
+
- movies
|
| 7 |
+
- series
|
| 8 |
+
- gsasrec
|
| 9 |
+
- onnx
|
| 10 |
+
language:
|
| 11 |
+
- en
|
| 12 |
+
- es
|
| 13 |
+
library_name: onnxruntime
|
| 14 |
+
pipeline_tag: other
|
| 15 |
+
---
|
| 16 |
+
|
| 17 |
+
# FrameLanguageLM
|
| 18 |
+
|
| 19 |
+
A language model where tokens are movies and TV series — trained on 32M viewing sequences to predict "the next film you'd love."
|
| 20 |
+
|
| 21 |
+
## Model Description
|
| 22 |
+
|
| 23 |
+
- **Architecture:** gSASRec (2-layer transformer, d=256, gBCE loss) with compositional embeddings
|
| 24 |
+
- **Vocabulary:** ~100k titles (movies + TV series) selected by IMDb numVotes
|
| 25 |
+
- **Training data:** MovieLens 32M sequences, enriched with IMDb/TMDB metadata
|
| 26 |
+
- **Cold-start:** ID-dropout (p=0.2) enables recommendation of unseen titles via metadata-only embeddings
|
| 27 |
+
- **Dual serving:** warm checkpoint (54k items with collaborative signal) + full checkpoint (100k including cold items)
|
| 28 |
+
|
| 29 |
+
## Performance
|
| 30 |
+
|
| 31 |
+
| Metric | Warm (feat) | Full (feat_iddrop) |
|
| 32 |
+
|--------|:-----------:|:------------------:|
|
| 33 |
+
| TEST NDCG@10 | 0.1174 | 0.1110 |
|
| 34 |
+
| TEST Recall@10 | 0.2164 | 0.2066 |
|
| 35 |
+
| TEST Recall@50 | 0.4630 | 0.4511 |
|
| 36 |
+
| Cold-start NDCG@10 | — | 0.0846 (63.7% of warm) |
|
| 37 |
+
|
| 38 |
+
Full ranking evaluation (no candidate sampling), leave-one-out temporal split. Total training cost: ~$1.25 (RTX 3090).
|
| 39 |
+
|
| 40 |
+
## How to Use
|
| 41 |
+
|
| 42 |
+
```bash
|
| 43 |
+
# Install the CLI
|
| 44 |
+
pip install frame-language-lm
|
| 45 |
+
|
| 46 |
+
# Import your FilmAffinity profile (auto-downloads model on first run)
|
| 47 |
+
frame-language-lm import --filmaffinity export.zip
|
| 48 |
+
|
| 49 |
+
# Get your gaps — movies you'd love but haven't seen
|
| 50 |
+
frame-language-lm gaps --top 50
|
| 51 |
+
|
| 52 |
+
# Check if a specific title is worth watching for you
|
| 53 |
+
frame-language-lm worth "Stalker"
|
| 54 |
+
|
| 55 |
+
# Find similar titles
|
| 56 |
+
frame-language-lm similar "Mulholland Drive"
|
| 57 |
+
|
| 58 |
+
# Search the catalog with filters
|
| 59 |
+
frame-language-lm search --director "Park Chan-wook" --country KR
|
| 60 |
+
```
|
| 61 |
+
|
| 62 |
+
## Files in this Repository
|
| 63 |
+
|
| 64 |
+
| File | Size | Description |
|
| 65 |
+
|------|------|-------------|
|
| 66 |
+
| `model_fp32.onnx` + `.data` | ~56 MB | Warm checkpoint (54k items with signal) |
|
| 67 |
+
| `model_full_fp32.onnx` + `.data` | ~101 MB | Full checkpoint (100k items, ID-dropout) |
|
| 68 |
+
| `item_embeddings.npy` | ~53 MB | Warm item embedding matrix |
|
| 69 |
+
| `item_embeddings_full.npy` | ~98 MB | Full item embedding matrix |
|
| 70 |
+
| `full_aux.npz` | ~0.2 MB | Cold-start flags (is_cold, is_movie, etc.) |
|
| 71 |
+
| `catalog.sqlite` | ~36 MB | Complete catalog with metadata |
|
| 72 |
+
| `vocab_map.json` | ~1 MB | Warm vocabulary mapping (tconst ↔ index) |
|
| 73 |
+
| `full_vocab_map.json` | ~2 MB | Full vocabulary mapping |
|
| 74 |
+
| `feature_vocabs.json` | ~2.4 MB | Compositional feature vocabularies |
|
| 75 |
+
| `meta.json` / `meta_full.json` | <1 KB each | Model configuration |
|
| 76 |
+
|
| 77 |
+
## Training Data & Licenses
|
| 78 |
+
|
| 79 |
+
- **MovieLens 32M:** F. Maxwell Harper and Joseph A. Konstan. 2015. *The MovieLens Datasets: History and Context.* ACM Transactions on Interactive Intelligent Systems (TiiS) 5, 4, Article 19. Non-commercial use; redistribution of transformations under same conditions.
|
| 80 |
+
- **IMDb Non-Commercial Datasets:** Personal and non-commercial use only. [developer.imdb.com](https://developer.imdb.com/non-commercial-datasets/)
|
| 81 |
+
- **TMDB API:** Metadata enrichment (country, language, budget). This product uses the TMDB API but is not endorsed, certified, or otherwise approved by TMDB.
|
| 82 |
+
|
| 83 |
+
## Limitations
|
| 84 |
+
|
| 85 |
+
- Trained on MovieLens (predominantly US/English mainstream cinema) — recommendations biased toward popular Western titles
|
| 86 |
+
- MovieLens ends Oct 2023 — post-2023 titles are cold-start only
|
| 87 |
+
- Series recommendations are metadata-based (no collaborative signal) — lower quality than films
|
| 88 |
+
- Budget feature: only 20-40% coverage in TMDB
|
| 89 |
+
|
| 90 |
+
## Ethical Considerations
|
| 91 |
+
|
| 92 |
+
- No user data is collected or transmitted — all inference runs locally
|
| 93 |
+
- Model cannot recover individual user viewing histories from training data
|
| 94 |
+
- Recommendations reflect statistical patterns in viewing behavior, not quality judgments
|
| 95 |
+
|
| 96 |
+
## Data Provenance & Takedown
|
| 97 |
+
|
| 98 |
+
This model was trained on publicly available, non-commercial datasets:
|
| 99 |
+
|
| 100 |
+
| Source | Data used | License | How to verify |
|
| 101 |
+
|--------|-----------|---------|---------------|
|
| 102 |
+
| MovieLens 32M | User viewing sequences | Custom (non-commercial, redistribution OK) | [grouplens.org](https://grouplens.org/datasets/movielens/32m/) |
|
| 103 |
+
| IMDb Non-Commercial | Titles, genres, directors, cast, year | Custom (personal/non-commercial) | [developer.imdb.com](https://developer.imdb.com/non-commercial-datasets/) |
|
| 104 |
+
| TMDB API | Country, language, budget (compositional features) | TMDB API ToS | [themoviedb.org](https://www.themoviedb.org/) |
|
| 105 |
+
|
| 106 |
+
For convenience, this repository includes a pre-built catalog (`catalog.sqlite`) containing metadata derived from IMDb and TMDB. If any rights holder objects to the inclusion of this catalog, it will be removed and users can rebuild it locally using the provided `scripts/build_catalog.py` script. Model weights are a mathematical transformation that cannot reconstruct the original training data.
|
| 107 |
+
|
| 108 |
+
**Takedown requests:** if you are a rights holder and believe any artifact in this repository infringes on your terms, please contact peportmel@gmail.com. We will respond within 72 hours and can remove the affected artifact or retrain the model excluding your data source at negligible cost (~$0.30, ~90 minutes of GPU time).
|
akas_es.json
ADDED
|
The diff for this file is too large to render.
See raw diff
|
|
|
catalog.sqlite
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:6499646c6fdf7b6d5d88fd1c7ed0953b65632e2eedec65e9a518b2f30227a823
|
| 3 |
+
size 37548032
|
feature_vocabs.json
ADDED
|
The diff for this file is too large to render.
See raw diff
|
|
|
full_aux.npz
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:86f9c130291b6fb799685961b5b6691f95f81547fded1a917df824746c3f728f
|
| 3 |
+
size 208120
|
full_vocab_map.json
ADDED
|
The diff for this file is too large to render.
See raw diff
|
|
|
item_embeddings.npy
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:4e00cdcbe6b633c4cbcc17d7179243bc5c3f106e281498109c7941efe7d06356
|
| 3 |
+
size 55351424
|
item_embeddings_full.npy
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:8013974fe435247132c5bc448f38c76d0eb9284511a6787b7d0d8fea0b0014ed
|
| 3 |
+
size 102401152
|
meta.json
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"checkpoint": "sasrec_feat.pt",
|
| 3 |
+
"checkpoint_epoch": 50,
|
| 4 |
+
"valid_ndcg@10": 0.13018137636965726,
|
| 5 |
+
"vocab_md5": "97774e35046472f8340a1b0bb4c751da",
|
| 6 |
+
"vocab_md5_actual": "97774e35046472f8340a1b0bb4c751da",
|
| 7 |
+
"n_items": 54053,
|
| 8 |
+
"d": 256,
|
| 9 |
+
"max_len": 200
|
| 10 |
+
}
|
meta_full.json
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"checkpoint": "sasrec_feat_iddrop.pt",
|
| 3 |
+
"checkpoint_epoch": 39,
|
| 4 |
+
"valid_ndcg@10": 0.12276953213630627,
|
| 5 |
+
"vocab_md5": "97774e35046472f8340a1b0bb4c751da",
|
| 6 |
+
"vocab_md5_actual": "97774e35046472f8340a1b0bb4c751da",
|
| 7 |
+
"n_items": 54053,
|
| 8 |
+
"d": 256,
|
| 9 |
+
"max_len": 200
|
| 10 |
+
}
|
model_fp32.onnx
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:5f605b98d542f94028999689aad4a70601f525b212ba102a7497aee909f24b2b
|
| 3 |
+
size 22808
|
model_fp32.onnx.data
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:f5449fa7a58fe2f3dbd3dc4562d51e9ce3299450f69a2903e75aee0615934cf6
|
| 3 |
+
size 59086848
|
model_full_fp32.onnx
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:a6f98f311a0d50ca20532ab3b15b482362c8f39ceebd149c57342831c62b1c07
|
| 3 |
+
size 22969
|
model_full_fp32.onnx.data
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:6e3bd12e8c6466057d8c3fb09f8b4880a7229d30e4156e993ec310624ea4587f
|
| 3 |
+
size 106136576
|
scripts/build_catalog.py
ADDED
|
@@ -0,0 +1,107 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Fase 1.4: ensambla el catalogo final y las secuencias de entrenamiento.
|
| 2 |
+
|
| 3 |
+
Uso: uv run python scripts/build_catalog.py
|
| 4 |
+
Entrada: data/interim/vocab.parquet + data/interim/tmdb.jsonl + data/raw/ml-32m/
|
| 5 |
+
Salida: data/catalog.sqlite + data/sequences.parquet
|
| 6 |
+
"""
|
| 7 |
+
|
| 8 |
+
import json
|
| 9 |
+
import sqlite3
|
| 10 |
+
import sys
|
| 11 |
+
from pathlib import Path
|
| 12 |
+
|
| 13 |
+
import duckdb
|
| 14 |
+
|
| 15 |
+
ROOT = Path(__file__).resolve().parent.parent
|
| 16 |
+
INTERIM = ROOT / "data" / "interim"
|
| 17 |
+
RAW = ROOT / "data" / "raw"
|
| 18 |
+
MIN_TMDB_COVERAGE = 0.80
|
| 19 |
+
|
| 20 |
+
vocab = duckdb.sql(
|
| 21 |
+
f"SELECT * FROM read_parquet('{(INTERIM / 'vocab.parquet').as_posix()}') ORDER BY numVotes DESC"
|
| 22 |
+
).fetchall()
|
| 23 |
+
cols = [
|
| 24 |
+
"tconst", "titleType", "primaryTitle", "originalTitle", "startYear",
|
| 25 |
+
"runtimeMinutes", "genres", "averageRating", "numVotes",
|
| 26 |
+
"directors", "cast", "tmdb_id_links",
|
| 27 |
+
]
|
| 28 |
+
|
| 29 |
+
tmdb: dict[str, dict] = {}
|
| 30 |
+
tmdb_path = INTERIM / "tmdb.jsonl"
|
| 31 |
+
if tmdb_path.exists():
|
| 32 |
+
with tmdb_path.open(encoding="utf-8") as f:
|
| 33 |
+
for line in f:
|
| 34 |
+
if line.strip():
|
| 35 |
+
rec = json.loads(line)
|
| 36 |
+
if "error" not in rec:
|
| 37 |
+
tmdb[rec["tconst"]] = rec
|
| 38 |
+
|
| 39 |
+
coverage = len([1 for row in vocab if row[0] in tmdb]) / len(vocab)
|
| 40 |
+
if coverage < MIN_TMDB_COVERAGE:
|
| 41 |
+
sys.exit(
|
| 42 |
+
f"cobertura TMDB insuficiente: {coverage:.1%} del vocabulario "
|
| 43 |
+
f"(minimo {MIN_TMDB_COVERAGE:.0%}). Lanza scripts/fetch_tmdb.py hasta completar."
|
| 44 |
+
)
|
| 45 |
+
|
| 46 |
+
db_path = ROOT / "data" / "catalog.sqlite"
|
| 47 |
+
db_path.unlink(missing_ok=True)
|
| 48 |
+
db = sqlite3.connect(db_path)
|
| 49 |
+
db.execute("""
|
| 50 |
+
CREATE TABLE items (
|
| 51 |
+
tconst TEXT PRIMARY KEY,
|
| 52 |
+
title_type TEXT NOT NULL,
|
| 53 |
+
primary_title TEXT NOT NULL,
|
| 54 |
+
original_title TEXT,
|
| 55 |
+
start_year INTEGER,
|
| 56 |
+
runtime_minutes INTEGER,
|
| 57 |
+
genres TEXT,
|
| 58 |
+
imdb_rating REAL,
|
| 59 |
+
num_votes INTEGER NOT NULL,
|
| 60 |
+
directors TEXT,
|
| 61 |
+
"cast" TEXT,
|
| 62 |
+
tmdb_id INTEGER,
|
| 63 |
+
media_type TEXT,
|
| 64 |
+
original_language TEXT,
|
| 65 |
+
countries TEXT,
|
| 66 |
+
budget INTEGER,
|
| 67 |
+
keywords TEXT,
|
| 68 |
+
poster_path TEXT,
|
| 69 |
+
popularity REAL
|
| 70 |
+
)
|
| 71 |
+
""")
|
| 72 |
+
|
| 73 |
+
rows = []
|
| 74 |
+
for row in vocab:
|
| 75 |
+
r = dict(zip(cols, row))
|
| 76 |
+
t = tmdb.get(r["tconst"], {})
|
| 77 |
+
rows.append((
|
| 78 |
+
r["tconst"], r["titleType"], r["primaryTitle"], r["originalTitle"],
|
| 79 |
+
r["startYear"], r["runtimeMinutes"], r["genres"], r["averageRating"],
|
| 80 |
+
r["numVotes"], r["directors"], r["cast"],
|
| 81 |
+
t.get("tmdb_id"), t.get("media_type"), t.get("original_language"),
|
| 82 |
+
"|".join(t.get("production_countries") or []) or None,
|
| 83 |
+
t.get("budget") or None,
|
| 84 |
+
"|".join(t.get("keywords") or []) or None,
|
| 85 |
+
t.get("poster_path"), t.get("popularity"),
|
| 86 |
+
))
|
| 87 |
+
db.executemany(f"INSERT INTO items VALUES ({','.join('?' * 19)})", rows)
|
| 88 |
+
db.execute("CREATE INDEX idx_items_title ON items (primary_title)")
|
| 89 |
+
db.commit()
|
| 90 |
+
db.close()
|
| 91 |
+
print(f"catalog.sqlite: {len(rows):,} items (cobertura TMDB {coverage:.1%})")
|
| 92 |
+
|
| 93 |
+
seq_out = ROOT / "data" / "sequences.parquet"
|
| 94 |
+
duckdb.sql(f"""
|
| 95 |
+
COPY (
|
| 96 |
+
SELECT m.userId, v.tconst, m.rating, m.timestamp
|
| 97 |
+
FROM read_csv_auto('{(RAW / "ml-32m" / "ratings.csv").as_posix()}') m
|
| 98 |
+
JOIN read_csv_auto('{(RAW / "ml-32m" / "links.csv").as_posix()}') l USING (movieId)
|
| 99 |
+
JOIN read_parquet('{(INTERIM / "vocab.parquet").as_posix()}') v
|
| 100 |
+
ON v.tconst = printf('tt%07d', CAST(l.imdbId AS BIGINT))
|
| 101 |
+
ORDER BY m.userId, m.timestamp
|
| 102 |
+
) TO '{seq_out.as_posix()}' (FORMAT PARQUET)
|
| 103 |
+
""")
|
| 104 |
+
n, users = duckdb.sql(
|
| 105 |
+
f"SELECT count(*), count(DISTINCT userId) FROM read_parquet('{seq_out.as_posix()}')"
|
| 106 |
+
).fetchone()
|
| 107 |
+
print(f"sequences.parquet: {n:,} interacciones de {users:,} usuarios")
|
vocab_map.json
ADDED
|
The diff for this file is too large to render.
See raw diff
|
|
|