| | 136 | }}} |
| | 137 | |
| | 138 | Целата Python скрипта: |
| | 139 | |
| | 140 | {{{ |
| | 141 | import psycopg2 |
| | 142 | from sentence_transformers import SentenceTransformer |
| | 143 | |
| | 144 | conn = psycopg2.connect( |
| | 145 | host="localhost", |
| | 146 | port=5433, |
| | 147 | database="booknest", |
| | 148 | user="postgres", |
| | 149 | password="postgres" |
| | 150 | ) |
| | 151 | |
| | 152 | cursor = conn.cursor() |
| | 153 | |
| | 154 | print("Connected!") |
| | 155 | |
| | 156 | cursor.execute( |
| | 157 | SELECT |
| | 158 | b.barcode, |
| | 159 | b.title, |
| | 160 | COALESCE(b.description, '') AS description, |
| | 161 | COALESCE(string_agg(DISTINCT g.name, ', '), '') AS genres, |
| | 162 | COALESCE(string_agg(DISTINCT c.name, ', '), '') AS categories, |
| | 163 | COALESCE(string_agg(DISTINCT a.first_name || ' ' || a.last_name, ', '), '') AS authors |
| | 164 | FROM book b |
| | 165 | LEFT JOIN book_genre bg ON bg.barcode = b.barcode |
| | 166 | LEFT JOIN genre g ON g.genre_id = bg.genre_id |
| | 167 | LEFT JOIN category_book cb ON cb.barcode = b.barcode |
| | 168 | LEFT JOIN category c ON c.category_id = cb.category_id |
| | 169 | LEFT JOIN book_author ba ON ba.barcode = b.barcode |
| | 170 | LEFT JOIN author a ON a.author_id = ba.author_id |
| | 171 | GROUP BY b.barcode, b.title, b.description |
| | 172 | LIMIT 10000 |
| | 173 | ) |
| | 174 | |
| | 175 | books = cursor.fetchall() |
| | 176 | |
| | 177 | print(f"Found {len(books)} books") |
| | 178 | |
| | 179 | model = SentenceTransformer("all-MiniLM-L6-v2") |
| | 180 | |
| | 181 | for i, (barcode, title, description, genres, categories, authors) in enumerate(books, start=1): |
| | 182 | text = ( |
| | 183 | title + " " + |
| | 184 | description + " " + |
| | 185 | genres + " " + |
| | 186 | categories + " " + |
| | 187 | authors |
| | 188 | ) |
| | 189 | |
| | 190 | embedding = model.encode(text).tolist() |
| | 191 | |
| | 192 | cursor.execute( |
| | 193 | |
| | 194 | UPDATE book |
| | 195 | SET embedding = %s |
| | 196 | WHERE barcode = %s |
| | 197 | """, |
| | 198 | (embedding, barcode) |
| | 199 | ) |
| | 200 | |
| | 201 | if i % 500 == 0: |
| | 202 | conn.commit() |
| | 203 | print(f"Updated {i}/{len(books)} books") |
| | 204 | |
| | 205 | |
| | 206 | conn.commit() |
| | 207 | |
| | 208 | print("DONE!") |
| | 209 | |
| | 210 | cursor.close() |
| | 211 | conn.close() |
| 179 | | 1. Ги наоѓа книгите што членот претходно ги има позајмено. |
| 180 | | 2. Го пресметува просечниот embedding профил на членот. |
| 181 | | 3. Ги разгледува сите останати книги. |
| 182 | | 4. Ги исклучува книгите што членот веќе ги има позајмено. |
| 183 | | 5. Ги рангира книгите според cosine similarity. |
| 184 | | 6. Ги враќа најдобрите N препораки. |
| | 256 | 1. Ги наоѓа книгите што членот претходно ги има позајмено. |
| | 257 | 2. Го пресметува просечниот embedding профил на членот. |
| | 258 | 3. Ги разгледува сите останати книги. |
| | 259 | 4. Ги исклучува книгите што членот веќе ги има позајмено. |
| | 260 | 5. Ги рангира книгите според cosine similarity. |
| | 261 | 6. Ги враќа најдобрите N препораки. |
| | 262 | |
| | 263 | Кодот на функцијата: |
| | 264 | |
| | 265 | {{{ |
| | 266 | CREATE OR REPLACE FUNCTION recommend_books_for_member_pgvector( |
| | 267 | p_member_id INT, |
| | 268 | p_limit INT DEFAULT 10 |
| | 269 | ) |
| | 270 | RETURNS TABLE ( |
| | 271 | barcode VARCHAR, |
| | 272 | title VARCHAR, |
| | 273 | similarity FLOAT |
| | 274 | ) |
| | 275 | LANGUAGE SQL |
| | 276 | AS $$ |
| | 277 | WITH borrowed_books AS ( |
| | 278 | SELECT DISTINCT bc.barcode |
| | 279 | FROM loan_history lh |
| | 280 | JOIN book_copy bc ON bc.copy_id = lh.copy_id |
| | 281 | WHERE lh.member_user_id = p_member_id |
| | 282 | ), |
| | 283 | member_profile AS ( |
| | 284 | SELECT AVG(b.embedding) AS profile_embedding |
| | 285 | FROM book b |
| | 286 | JOIN borrowed_books bb ON bb.barcode = b.barcode |
| | 287 | WHERE b.embedding IS NOT NULL |
| | 288 | ) |
| | 289 | SELECT |
| | 290 | b.barcode, |
| | 291 | b.title, |
| | 292 | 1 - (b.embedding <=> mp.profile_embedding) AS similarity |
| | 293 | FROM book b |
| | 294 | CROSS JOIN member_profile mp |
| | 295 | WHERE b.embedding IS NOT NULL |
| | 296 | AND b.barcode NOT IN (SELECT barcode FROM borrowed_books) |
| | 297 | ORDER BY b.embedding <=> mp.profile_embedding |
| | 298 | LIMIT p_limit; |
| | 299 | $$; |
| | 300 | }}} |
| 282 | | [PLACEHOLDER: Screenshot од препораки со genre/category/author] |
| | 398 | {{{ |
| | 399 | SELECT |
| | 400 | r.barcode, |
| | 401 | r.title, |
| | 402 | r.similarity, |
| | 403 | string_agg(DISTINCT g.name, ', ') AS genres, |
| | 404 | string_agg(DISTINCT c.name, ', ') AS categories, |
| | 405 | string_agg(DISTINCT a.first_name || ' ' || a.last_name, ', ') AS authors |
| | 406 | FROM recommend_books_for_member_pgvector(10835, 10) r |
| | 407 | LEFT JOIN book_genre bg ON bg.barcode = r.barcode |
| | 408 | LEFT JOIN genre g ON g.genre_id = bg.genre_id |
| | 409 | LEFT JOIN category_book cb ON cb.barcode = r.barcode |
| | 410 | LEFT JOIN category c ON c.category_id = cb.category_id |
| | 411 | LEFT JOIN book_author ba ON ba.barcode = r.barcode |
| | 412 | LEFT JOIN author a ON a.author_id = ba.author_id |
| | 413 | GROUP BY r.barcode, r.title, r.similarity |
| | 414 | ORDER BY r.similarity DESC; |
| | 415 | }}} |
| | 416 | |
| | 417 | [[Image(recommendations_with_metadata.png,800px)]] |
| 348 | | == Screenshots / Proof of Work == |
| 349 | | |
| 350 | | [[Image(vector_extension.png)]] |
| 351 | | |
| 352 | | Проверка дека pgvector екстензијата е успешно инсталирана. |
| 353 | | |
| 354 | | [[Image(embeddings_generated.png)]] |
| 355 | | |
| 356 | | Потврда дека се генерирани embeddings за 10000 книги. |
| 357 | | |
| 358 | | [[Image(recommendation_results.png)]] |
| 359 | | |
| 360 | | Резултат од функцијата recommend_books_for_member_pgvector. |
| 361 | | |
| 362 | | [[Image(validation_result.png)]] |
| 363 | | |
| 364 | | Проверка дека не се препорачуваат веќе позајмени книги. |
| 365 | | |
| 366 | | [[Image(loan_history.png)]] |
| 367 | | |
| 368 | | Пример книги од претходната историја на позајмувања. |