Mesin rekomendasi pada modul ini dirancang sebagai kumpulan pola graf, bukan sebagai satu algoritma tunggal. Topik ini merancang tiga pola rekomendasi di atas model data :Pengguna, :Kursus, dan :MENGIKUTI {progres}, yaitu konsumsi bersama, kemiripan item, dan sinyal konten, lalu merumuskan masing-masing pola menjadi kueri Cypher yang dijalankan pada dataset modul. Alur satu rekomendasi dua lompatan digambarkan dengan diagram, dan bagian akhir membahas sinyal progres yang tidak dihitung oleh saringan beserta konsekuensinya terhadap urutan kandidat.
Model data dan pertanyaan rekomendasi#
Model data rekomendasi menggunakan dua label node dan satu tipe relasi, yaitu :Pengguna dengan properti nama, :Kursus dengan properti judul dan kategori, serta relasi :MENGIKUTI yang mengarah dari pengguna menuju kursus yang diikutinya. Setiap relasi membawa properti progres berupa bilangan bulat 0 sampai 100. Properti kategori memuat kelompok materi seperti Data atau Pemrograman dan menjadi bahan sinyal ketiga, sedangkan progres menjadi bahan keputusan penyaringan pada bagian akhir.
Pertanyaan rekomendasi dirumuskan langsung sebagai pola graf. Bagi satu pengguna acuan, kandidatnya adalah kursus yang diikuti pengguna lain yang juga mengikuti kursus yang sama dengan pengguna acuan, tetapi belum diikuti pengguna acuan itu sendiri. Rumusan tersebut diterjemahkan menjadi pola (u)-[:MENGIKUTI]->(k)<-[:MENGIKUTI]-(lain)-[:MENGIKUTI]->(t), dan setiap kueri pada topik ini merupakan penjabaran bentuk itu.
Skrip setup.cypher membangun ulang dataset sehingga dapat dijalankan berulang. Roster enam pengguna, enam kursus, dan sembilan belas relasi :MENGIKUTI-nya sama dengan dataset konsumsi kursus yang digunakan seluruh topik pada modul ini, sedangkan tambahan khusus topik ini berupa properti kategori pada setiap kursus untuk kebutuhan sinyal konten. Pembersihan di awal skrip terbatas pada enam nama pengguna dan enam judul kursus milik modul, sehingga dataset latihan lain pada instance yang sama tidak tersentuh.
// Dataset roster pembelajaran untuk topik Merancang mesin rekomendasi.
// Roster sama dengan dataset modul pada topik Di mana graf menang dan kalah,
// yaitu enam pengguna, enam kursus, dan relasi MENGIKUTI yang membawa properti
// progres berupa bilangan bulat 0-100.
// Tambahan topik ini berupa properti kategori pada setiap kursus sebagai bahan
// sinyal konten.
// Instance latihan dipakai bersama oleh dataset dari beberapa topik, sehingga
// pembersihan di awal hanya menyentuh nama-nama milik modul ini.
// Skrip ini idempoten (idempotent), artinya aman dijalankan berulang kali.
// Pembersihan terbatas pada nama-nama milik modul ini
MATCH (p:Pengguna)
WHERE p.nama IN ["Hesti", "Indra", "Joko", "Kartika", "Laila", "Maya"]
DETACH DELETE p;
MATCH (k:Kursus)
WHERE k.judul IN ["Pengantar Algoritma", "Basis Data Graf", "Statistika Terapan",
"Pemrograman Python", "Jaringan Komputer", "Rekayasa Perangkat Lunak"]
DETACH DELETE k;
// Node pengguna: label :Pengguna dengan properti nama.
MERGE (:Pengguna {nama: "Hesti"});
MERGE (:Pengguna {nama: "Indra"});
MERGE (:Pengguna {nama: "Joko"});
MERGE (:Pengguna {nama: "Kartika"});
MERGE (:Pengguna {nama: "Laila"});
MERGE (:Pengguna {nama: "Maya"});
// Node kursus: label :Kursus dengan properti judul.
MERGE (:Kursus {judul: "Pengantar Algoritma"});
MERGE (:Kursus {judul: "Basis Data Graf"});
MERGE (:Kursus {judul: "Statistika Terapan"});
MERGE (:Kursus {judul: "Pemrograman Python"});
MERGE (:Kursus {judul: "Jaringan Komputer"});
MERGE (:Kursus {judul: "Rekayasa Perangkat Lunak"});
// Kategori kursus: tambahan topik Merancang mesin rekomendasi sebagai bahan
// sinyal konten.
MATCH (k:Kursus {judul: "Pengantar Algoritma"}) SET k.kategori = "Algoritma";
MATCH (k:Kursus {judul: "Basis Data Graf"}) SET k.kategori = "Basis Data";
MATCH (k:Kursus {judul: "Statistika Terapan"}) SET k.kategori = "Data";
MATCH (k:Kursus {judul: "Pemrograman Python"}) SET k.kategori = "Pemrograman";
MATCH (k:Kursus {judul: "Jaringan Komputer"}) SET k.kategori = "Jaringan";
MATCH (k:Kursus {judul: "Rekayasa Perangkat Lunak"}) SET k.kategori = "Pemrograman";
// Relasi MENGIKUTI berarah dari pengguna menuju kursus, satu relasi per
// pasangan pengguna dan kursus, dengan properti progres.
MATCH (p:Pengguna {nama: "Hesti"}), (k:Kursus {judul: "Pengantar Algoritma"})
MERGE (p)-[r:MENGIKUTI]->(k)
SET r.progres = 80;
MATCH (p:Pengguna {nama: "Hesti"}), (k:Kursus {judul: "Basis Data Graf"})
MERGE (p)-[r:MENGIKUTI]->(k)
SET r.progres = 65;
MATCH (p:Pengguna {nama: "Hesti"}), (k:Kursus {judul: "Pemrograman Python"})
MERGE (p)-[r:MENGIKUTI]->(k)
SET r.progres = 90;
MATCH (p:Pengguna {nama: "Hesti"}), (k:Kursus {judul: "Rekayasa Perangkat Lunak"})
MERGE (p)-[r:MENGIKUTI]->(k)
SET r.progres = 40;
MATCH (p:Pengguna {nama: "Indra"}), (k:Kursus {judul: "Pengantar Algoritma"})
MERGE (p)-[r:MENGIKUTI]->(k)
SET r.progres = 75;
MATCH (p:Pengguna {nama: "Indra"}), (k:Kursus {judul: "Pemrograman Python"})
MERGE (p)-[r:MENGIKUTI]->(k)
SET r.progres = 60;
MATCH (p:Pengguna {nama: "Indra"}), (k:Kursus {judul: "Statistika Terapan"})
MERGE (p)-[r:MENGIKUTI]->(k)
SET r.progres = 45;
MATCH (p:Pengguna {nama: "Joko"}), (k:Kursus {judul: "Basis Data Graf"})
MERGE (p)-[r:MENGIKUTI]->(k)
SET r.progres = 55;
MATCH (p:Pengguna {nama: "Joko"}), (k:Kursus {judul: "Jaringan Komputer"})
MERGE (p)-[r:MENGIKUTI]->(k)
SET r.progres = 70;
MATCH (p:Pengguna {nama: "Kartika"}), (k:Kursus {judul: "Pengantar Algoritma"})
MERGE (p)-[r:MENGIKUTI]->(k)
SET r.progres = 70;
MATCH (p:Pengguna {nama: "Kartika"}), (k:Kursus {judul: "Basis Data Graf"})
MERGE (p)-[r:MENGIKUTI]->(k)
SET r.progres = 60;
MATCH (p:Pengguna {nama: "Kartika"}), (k:Kursus {judul: "Pemrograman Python"})
MERGE (p)-[r:MENGIKUTI]->(k)
SET r.progres = 85;
MATCH (p:Pengguna {nama: "Kartika"}), (k:Kursus {judul: "Jaringan Komputer"})
MERGE (p)-[r:MENGIKUTI]->(k)
SET r.progres = 50;
MATCH (p:Pengguna {nama: "Laila"}), (k:Kursus {judul: "Statistika Terapan"})
MERGE (p)-[r:MENGIKUTI]->(k)
SET r.progres = 80;
MATCH (p:Pengguna {nama: "Laila"}), (k:Kursus {judul: "Pemrograman Python"})
MERGE (p)-[r:MENGIKUTI]->(k)
SET r.progres = 50;
MATCH (p:Pengguna {nama: "Laila"}), (k:Kursus {judul: "Jaringan Komputer"})
MERGE (p)-[r:MENGIKUTI]->(k)
SET r.progres = 35;
MATCH (p:Pengguna {nama: "Maya"}), (k:Kursus {judul: "Basis Data Graf"})
MERGE (p)-[r:MENGIKUTI]->(k)
SET r.progres = 90;
MATCH (p:Pengguna {nama: "Maya"}), (k:Kursus {judul: "Statistika Terapan"})
MERGE (p)-[r:MENGIKUTI]->(k)
SET r.progres = 65;
MATCH (p:Pengguna {nama: "Maya"}), (k:Kursus {judul: "Rekayasa Perangkat Lunak"})
MERGE (p)-[r:MENGIKUTI]->(k)
SET r.progres = 30;# tidak-dijalankan
# membangun ulang dataset rekomendasi melalui pipa stdin ke cypher-shell
cat setup.cypher | cypher-shell -a bolt://127.0.0.1:7687 -u neo4jKelengkapan pemuatan diperiksa dengan menghitung relasi yang kedua ujungnya terjangkar pada roster modul. Penghitungan yang diberi daftar nama dan judul eksplisit menjaga hasilnya tetap mengenai dataset modul ini saja, karena instance yang sama juga memuat roster pendaftaran lain.
MATCH (p:Pengguna)-[r:MENGIKUTI]->(k:Kursus)
WHERE p.nama IN ["Hesti", "Indra", "Joko", "Kartika", "Laila", "Maya"]
AND k.judul IN ["Pengantar Algoritma", "Basis Data Graf", "Statistika Terapan",
"Pemrograman Python", "Jaringan Komputer", "Rekayasa Perangkat Lunak"]
RETURN count(r) AS jumlahRelasi;jumlahRelasi
19Konsumsi bersama sebagai pola pertama#
Konsumsi bersama (co-consumption) menjadikan dua pengguna yang mengikuti kursus yang sama sebagai sinyal satu sama lain. Pada dataset modul, Hesti dan Kartika mengikuti tiga kursus yang sama, yaitu Pengantar Algoritma, Basis Data Graf, dan Pemrograman Python, sehingga kandidat bagi Hesti muncul secara alami dari pendaftaran lain milik Kartika. Pola ini menelusuri dua lompatan, dan diagram berikut menggambarkan satu jalurnya dari Hesti melalui Kartika menuju Jaringan Komputer.
Kueri berikut menelusuri kedua lompatan dalam satu pola MATCH. Variabel lain mengikat pengguna perantara, predikat NOT (u)-[:MENGIKUTI]->(t) menyisihkan kursus yang sudah diikuti Hesti, dan count(DISTINCT lain) menghitung setiap perantara sekali meskipun perantara itu terhubung melalui beberapa kursus.
MATCH (u:Pengguna {nama: "Hesti"})-[:MENGIKUTI]->(k:Kursus)<-[:MENGIKUTI]-(lain:Pengguna)-[:MENGIKUTI]->(t:Kursus)
WHERE NOT (u)-[:MENGIKUTI]->(t)
RETURN t.judul AS rekomendasi, count(DISTINCT lain) AS pengguna_bersama
ORDER BY pengguna_bersama DESC, rekomendasi;rekomendasi, pengguna_bersama
"Jaringan Komputer", 3
"Statistika Terapan", 3Jaringan Komputer dan Statistika Terapan mengumpulkan tiga pemberi sinyal yang sama banyak, sehingga kedudukannya seri dan urutan tampil pada keluaran hanya ditentukan abjad. Kartika menyumbang sinyal bagi Jaringan Komputer melalui ketiga kursus bersamanya dengan Hesti, sedangkan Indra, Maya, dan Laila menjadi perantara bagi Statistika Terapan. Peringkat pada tahap ini murni penghitungan pendaftaran, tanpa menimbang seberapa jauh setiap pengikut mengerjakan materinya.
Kemiripan item sebagai pola kedua#
Kemiripan item (item similarity) memindahkan sudut pandang dari satu pengguna ke pasangan kursus. Setiap pengguna yang mengikuti dua kursus sekaligus menaikkan kekuatan pasangan itu sebanyak satu, sehingga pasangan yang diikuti bersama oleh banyak pengguna naik ke urutan teratas. Kondisi a.judul < b.judul menjaga setiap pasangan dihitung satu kali, dan saringan pada p.nama menjangkau roster modul ini saja karena instance yang sama memuat dataset pendaftaran lain.
MATCH (a:Kursus)<-[:MENGIKUTI]-(p:Pengguna)-[:MENGIKUTI]->(b:Kursus)
WHERE p.nama IN ["Hesti", "Indra", "Joko", "Kartika", "Laila", "Maya"] AND a.judul < b.judul
RETURN a.judul AS kursus_a, b.judul AS kursus_b, count(DISTINCT p) AS pengguna_bersama
ORDER BY pengguna_bersama DESC, kursus_a, kursus_b
LIMIT 3;kursus_a, kursus_b, pengguna_bersama
"Pemrograman Python", "Pengantar Algoritma", 3
"Basis Data Graf", "Jaringan Komputer", 2
"Basis Data Graf", "Pemrograman Python", 2Pasangan Pemrograman Python dan Pengantar Algoritma dikumpulkan oleh tiga pengguna, yaitu Hesti, Indra, dan Kartika, sedangkan dua pasangan berikutnya masing-masing dikumpulkan dua pengguna. Pasangan-pasangan yang seri pada angka dua diurutkan oleh kunci kursus_a, kursus_b pada ORDER BY, sehingga tiga baris yang tampil bersifat tetap pada setiap eksekusi. Hitungan ini adalah bentuk kemiripan item yang paling sederhana. Pada graf berukuran nyata, nilai per pasangan biasanya dituliskan kembali sebagai relasi berbobot antar kursus atau dihitung dengan algoritma kemiripan pada pustaka GDS agar kandidat tidak dirakit ulang dari seluruh pendaftaran setiap kali dibutuhkan, sedangkan pada dataset sekecil ini pola MATCH cukup dihitung langsung.
Sinyal konten sebagai pola ketiga#
Sinyal konten (content-based signal) menggunakan properti item, bukan riwayat pendaftaran. Dua kursus dalam kategori yang sama diperlakukan serupa, sehingga kandidat bagi seorang pengguna adalah kursus sekategori dengan kursus yang sudah diikutinya. Pola ini sama sekali tidak menyentuh pengguna lain, sehingga tetap menghasilkan kandidat meskipun perilaku pendaftaran di sekitar itemnya minim. Pada dataset modul, hanya kategori Pemrograman yang beranggotakan dua kursus, sedangkan empat kategori lainnya beranggotakan satu kursus.
MATCH (u:Pengguna {nama: "Indra"})-[:MENGIKUTI]->(k:Kursus)
MATCH (t:Kursus {kategori: k.kategori})
WHERE t <> k AND NOT (u)-[:MENGIKUTI]->(t)
RETURN k.judul AS diikuti, t.judul AS kandidat, k.kategori AS kategori
ORDER BY diikuti, kandidat;diikuti, kandidat, kategori
"Pemrograman Python", "Rekayasa Perangkat Lunak", "Pemrograman"Kandidat tunggal pada keluaran berasal dari pasangan Pemrograman Python dan Rekayasa Perangkat Lunak yang sama-sama berkategori Pemrograman. Kursus lain yang diikuti Indra tidak menyumbang kandidat karena Pengantar Algoritma merupakan satu-satunya kursus berkategori Algoritma, sedangkan Statistika Terapan satu-satunya kursus berkategori Data. Kategori yang anggotanya baru satu belum menyumbang pasangan apa pun, sehingga jangkauan pola ini bergantung pada seberapa banyak kursus yang tergabung dalam setiap kategori. Pola konten tetap melengkapi dua pola sebelumnya karena bekerja pada atribut item yang tidak bergantung pada riwayat pendaftaran, sehingga atribut itu dapat digunakan sejak item pertama kali masuk katalog.
Sinyal progres yang disaring dan konsekuensinya#
Ketiga pola di atas menghitung setiap relasi :MENGIKUTI dengan bobot yang sama, sehingga properti progres tidak digunakan sebagai penimbang. Konsekuensinya, pendaftaran yang baru dimulai memberi sinyal penuh. Laila yang progresnya pada Jaringan Komputer baru 35 mengangkat kursus itu dengan bobot yang sama seperti Joko yang progresnya 70, padahal pendaftaran yang masih di awal materi belum tentu mencerminkan ketertarikan yang sungguh-sungguh.
Kueri berikut mengulang rekomendasi bagi Hesti dengan satu tambahan saringan, yaitu pendaftaran perantara pada kursus kandidat yang progresnya di bawah 60 tidak dihitung. Saringan ditempatkan pada lompatan kedua karena pemberi sinyal yang relevan adalah pengguna yang benar-benar maju pada kursus yang direkomendasikan.
MATCH (u:Pengguna {nama: "Hesti"})-[:MENGIKUTI]->(k:Kursus)<-[:MENGIKUTI]-(lain:Pengguna)-[r:MENGIKUTI]->(t:Kursus)
WHERE r.progres >= 60 AND NOT (u)-[:MENGIKUTI]->(t)
RETURN t.judul AS rekomendasi, count(DISTINCT lain) AS pengguna_bersama
ORDER BY pengguna_bersama DESC, rekomendasi;rekomendasi, pengguna_bersama
"Statistika Terapan", 2
"Jaringan Komputer", 1Perbandingan kedua keluaran memperlihatkan efek saringan yang konkret. Indra dengan progres 45, Kartika dengan progres 50, dan Laila dengan progres 35 tidak lagi terhitung sebagai pemberi sinyal, sehingga Jaringan Komputer tersisa satu pemberi sinyal, sedangkan Statistika Terapan tetap didukung Maya dan Laila. Keputusan membuang atau menggunakan progres dengan demikian merupakan bagian dari perancangan, bukan pelengkap kueri.
Kesalahan umum#
Kesalahan pertama adalah menghitung jalur alih-alih pengguna. count(*) pada pola konsumsi bersama menghitung setiap jalur dua lompatan, sehingga satu perantara yang berbagi beberapa kursus dihitung ulang berkali-kali. Kartika misalnya terhitung tiga kali bagi Jaringan Komputer karena perantaraannya melalui tiga kursus. Keluaran berikut memperlihatkan pembengkakan itu.
MATCH (u:Pengguna {nama: "Hesti"})-[:MENGIKUTI]->(k:Kursus)<-[:MENGIKUTI]-(lain:Pengguna)-[:MENGIKUTI]->(t:Kursus)
WHERE NOT (u)-[:MENGIKUTI]->(t)
RETURN t.judul AS rekomendasi, count(*) AS jumlahJalur
ORDER BY jumlahJalur DESC, rekomendasi;rekomendasi, jumlahJalur
"Jaringan Komputer", 5
"Statistika Terapan", 5Angka 5 pada kedua baris adalah jumlah jalur, bukan jumlah pengguna, dan kedudukan serinya menyesatkan karena dibangun dari penghitungan yang salah. Perbaikannya adalah menghitung perantara dengan count(DISTINCT lain) sebagaimana kueri utama konsumsi bersama. Dengan penghitungan itu, setiap pengguna hanya dinilai satu kali berapa pun kursus bersama yang dilaluinya.
Kesalahan kedua adalah menjangkau dataset lain pada instance yang sama. Tanpa saringan pada p.nama, pola kemiripan item ikut menghitung pasangan kursus milik roster pendaftaran lain. Percobaan tanpa saringan menghasilkan 37 jalur, sedangkan roster modul ini hanya menyumbang 22. Selisih lima belas jalur itu berasal dari pasangan milik empat pengguna pendaftaran lain pada instance yang sama. Perbaikannya adalah menjangkarkan penghitungan pada daftar p.nama yang eksplisit atau pada judul kursus milik modul.
Ringkasan#
- Tiga pola rekomendasi dirumuskan sebagai pola graf pada model
:Pengguna,:Kursus, dan:MENGIKUTI {progres}, yaitu konsumsi bersama dua lompatan, kemiripan pasangan kursus, dan kecocokan kategori. - Konsumsi bersama bagi Hesti menghasilkan dua kandidat seri dengan tiga pemberi sinyal, yaitu Jaringan Komputer dan Statistika Terapan, karena Hesti dan Kartika berbagi tiga kursus.
- Sinyal konten tetap menghasilkan kandidat dari properti
kategoritanpa menyentuh riwayat pengguna lain, sehingga item baru pada katalog tetap terjangkau. - Saringan progres minimal 60 pada lompatan kedua memecah keadaan seri dan memindahkan puncak peringkat ke Statistika Terapan, sehingga pilihan ambang menjadi bagian dari desain.
Referensi#
- Patterns — Neo4j Docs — Referensi pola Cypher untuk penelusuran dua lompatan dan predikat pola pada kueri rekomendasi.
- Node Similarity — Neo4j Docs — Algoritma kemiripan item pada pustaka GDS untuk menghitung pasangan kursus pada graf berukuran nyata.
- What is graph data modeling? — Neo4j Docs — Panduan penempatan label, relasi, dan properti pada model data graf.