Рекомендаційні системи — це те, що змушує людей годинами сидіти в Netflix або Amazon, бо наступний контент завжди “саме те”. Цей туторіал покаже, як зібрати власну рекомендаційну систему на AI без глибоких знань машинного навчання — від підготовки даних до робочого прототипу. Весь процес займе від 3 до 5 годин, а результатом буде Python-додаток, який рекомендує товари, фільми або статті на основі поведінки користувача. Для старту потрібен базовий Python (рівень “можу запустити скрипт”) та обліковий запис OpenAI або Cohere.
🛠️ Що знадобиться
- Python 3.10+ — основне середовище для коду; безкоштовний, завантажити на python.org
- OpenAI API або Cohere API — для генерації векторних ембедингів (embeddings); є безкоштовний тріал на $5 у OpenAI або 100 000 токенів/місяць безкоштовно у Cohere
- Pinecone або Chroma — векторна база даних для зберігання та пошуку схожих об’єктів; Chroma повністю безкоштовний і локальний, Pinecone має безкоштовний tier
- VS Code або Google Colab — редактор коду; Colab зручний якщо не хочеш нічого встановлювати локально
- Pandas — для роботи з датасетом; безкоштовна бібліотека Python
- Датасет MovieLens (ml-latest-small) — готовий набір даних з оцінками фільмів від реальних користувачів; безкоштовний, завантажити на grouplens.org
📋 Покрокова інструкція
Крок 1: Встановлення середовища та завантаження даних
Відкрий термінал і виконай команду pip install openai cohere chromadb pandas numpy scikit-learn — це встановить всі потрібні бібліотеки за одну операцію. Далі зайди на сайт grouplens.org, знайди розділ “MovieLens Latest Datasets” і завантаж файл ml-latest-small.zip (розмір ~1 МБ). Розпакуй архів у папку проєкту — тобі знадобляться файли movies.csv та ratings.csv. Створи новий файл recommender.py у VS Code і на початку додай імпорти: import pandas as pd, import numpy as np, import chromadb, from openai import OpenAI.

Крок 2: Підготовка та очищення датасету
Завантаж дані в Pandas: напиши movies = pd.read_csv('movies.csv') та ratings = pd.read_csv('ratings.csv'). Об’єднай таблиці командою df = ratings.merge(movies, on='movieId') — це створить єдину таблицю з полями userId, movieId, rating, title, genres. Відфільтруй тільки активних користувачів, щоб уникнути шуму: active_users = df.groupby('userId').filter(lambda x: len(x) >= 20). Важливий підводний камінь: колонка genres містить значення типу “Action|Comedy|Drama” через вертикальну риску — це буде корисно пізніше при формуванні тексту для ембедингів, не перетворюй її в список одразу.
Крок 3: Генерація векторних ембедингів для фільмів
Тепер найцікавіше — перетворимо кожен фільм на числовий вектор, який AI “розуміє”. Створи функцію, яка для кожного фільму формує текстовий опис: def make_description(row): return f"Фільм: {row['title']}. Жанри: {row['genres'].replace('|', ', ')}". Ініціалізуй клієнт OpenAI: client = OpenAI(api_key="ВАШ_КЛЮЧ") — ключ беремо на platform.openai.com у розділі API Keys. Напиши функцію для отримання ембедингу: def get_embedding(text): response = client.embeddings.create(input=text, model="text-embedding-3-small"); return response.data[0].embedding. Обробляй фільми батчами по 50 штук із затримкою time.sleep(0.5) між запитами — так не перевищиш ліміти API і не отримаєш помилку 429.
Крок 4: Збереження ембедингів у векторну базу Chroma
Ініціалізуй локальну Chroma базу: chroma_client = chromadb.PersistentClient(path="./chroma_db") та collection = chroma_client.get_or_create_collection(name="movies"). Далі в циклі додавай кожен фільм: collection.add(embeddings=[embedding], documents=[description], ids=[str(movie_id)], metadatas=[{"title": title, "genres": genres}]). Після першого запуску база збережеться в папку ./chroma_db — наступного разу не потрібно знову генерувати ембединги, просто підключайся до існуючої бази через той самий PersistentClient. Перевір що все записалось: виконай print(collection.count()) — має вивести кількість фільмів у базі (близько 9 000 для ml-latest-small).
Крок 5: Побудова рекомендаційного движка та тестування
Тепер будуємо логіку рекомендацій. Для конкретного користувача знайди його топ-5 фільмів за оцінкою: user_top = df[df['userId'] == user_id].nlargest(5, 'rating'). Об’єднай їх описи в один рядок і отримай “профільний ембединг” користувача: profile_text = " ".join([make_description(row) for _, row in user_top.iterrows()]); user_vector = get_embedding(profile_text). Зроби запит до Chroma: results = collection.query(query_embeddings=[user_vector], n_results=10, where={"movieId": {"$nin": list(user_top['movieId'])}}) — параметр $nin виключає вже переглянуті фільми. Запусти скрипт командою python recommender.py і передай будь-який userId з датасету — на виході отримаєш список 10 рекомендованих фільмів з відсотком схожості. Фінальний результат: персоналізовані рекомендації, що враховують жанрові вподобання конкретного глядача.
⚠️ Типові помилки та як їх уникнути
- Перевищення ліміту OpenAI API на старті — не намагайся згенерувати ембединги для всіх 9 000 фільмів одним запитом; розбий на батчі по 50 і додай
time.sleep(1), інакше отримаєш помилку RateLimitError і твій скрипт впаде на середині - Дублювання записів у Chroma при перезапуску — якщо запускаєш скрипт вдруге, Chroma кине помилку про дублікати ID; використовуй
get_or_create_collectionі перевіряйcollection.get(ids=[str(movie_id)])перед додаванням нового запису - Холодний старт для нових користувачів — якщо у користувача менше 3 оцінок, профільний ембединг буде ненадійним; для таких юзерів замість персоналізації показуй топ-10 найпопулярніших фільмів (відфільтруй за кількістю оцінок у ratings.csv)
- Неправильний формат ID у Chroma — Chroma вимагає ID виключно у форматі рядка (string); якщо передаєш integer movieId напряму, отримаєш TypeError; завжди роби
str(movie_id)при додаванні та запиті
💡 Поради для кращого результату
По-перше, збагачуй текстовий опис фільму додатковими метаданими: якщо підключиш безкоштовний TMDB API (themoviedb.org), зможеш додати до опису рік виходу, режисера та короткий синопсис — якість рекомендацій зросте помітно, бо ембединг “розуміє” більше контексту. По-друге, замість одного профільного ембедингу користувача спробуй зважену середню: фільм з оцінкою 5.0 має вплив weight=2.0, а з оцінкою 3.0 — лише weight=0.5; реалізується через np.average(embeddings, axis=0, weights=weights) і дає значно точніші результати. По-третє, додай фільтр “різноманітності”: якщо всі 10 рекомендацій виявились бойовиками, обмеж кожен жанр до 3 результатів через post-processing — користувачі цінують несподівані відкриття. По-четверте, логуй результати запитів у простий CSV-файл: так через тиждень зможеш проаналізувати, які рекомендації були клікнуті, і використати це як implicit feedback для покращення системи.
❓ Часті запитання (FAQ)
1. Чи можна використати цю систему не для фільмів, а для товарів інтернет-магазину?
Так, логіка абсолютно та сама. Замість movies.csv підготуй CSV з полями product_id, name, category, description — і формуй текстовий опис як “Товар: {name}. Категорія: {category}. {description}”. Решта коду залишається без змін.

2. Скільки коштуватиме API OpenAI для генерації ембедингів?
Модель text-embedding-3-small коштує $0.02 за 1 мільйон токенів. Для датасету ml-latest-small (9 000 фільмів) це вийде менше $0.05 за весь запуск — буквально копійки. Після першого запуску ембединги зберігаються в Chroma і більше не генеруються.
3. Як додати реальний веб-інтерфейс до цієї системи?
Найшвидший варіант — обгорни свій движок у FastAPI (2-3 роути: POST /user/{id}/recommendations, GET /movie/{id}/similar) і задеплой на Railway.app або Render.com безкоштовно. Фронтенд можна зробити за годину на Streamlit командою pip install streamlit.
4. Що краще — Chroma чи Pinecone для продакшн-версії?
Chroma ідеальна для локальної розробки та невеликих проєктів до 100 000 векторів. Якщо плануєш масштабування і хочеш хмарне рішення з реплікацією — переходь на Pinecone або Weaviate; міграція займе буквально заміну клієнта в одному місці коду, бо інтерфейс запитів схожий.
5. Чи можна побудувати систему без зовнішніх API, повністю локально?
Так, заміни OpenAI на локальну модель через бібліотеку sentence-transformers: from sentence_transformers import SentenceTransformer; model = SentenceTransformer('all-MiniLM-L6-v2') — ця модель безкоштовна, працює офлайн і генерує якісні ембединги, хоча трохи поступається GPT-based моделям у точності.
🏁 Підсумок
Ти щойно побудував персоналізовану рекомендаційну систему, яка використовує векторні ембединги для розуміння вподобань користувача та знаходить схожий контент за семантичною близькістю — саме так працюють системи Netflix і Spotify під капотом. Результат — робочий Python-скрипт, що приймає userId і повертає список персоналізованих рекомендацій з урахуванням виключення вже переглянутого контенту.
Починай прямо зараз: завантаж MovieLens датасет з grouplens.org, зареєструйся на platform.openai.com і отримай API-ключ — перші результати побачиш вже за годину. Коли базова версія запрацює, зроби наступний крок і додай Streamlit-інтерфейс, щоб показати проєкт у портфоліо.
РОЗСИЛКА
📬 Щотижневий AI-дайджест
Найкращі статті про ШІ та автоматизацію — без спаму, лише суть
Без спаму · Відписатись будь-коли

