Механизмът за препоръки (понякога наричан система за препоръки) е инструмент, който позволява разработчици на алгоритми предскажете какво може или не може да се хареса на потребителя сред списък с дадени елементи. Механизмите за препоръки са доста интересна алтернатива на полетата за търсене, тъй като механизмите за препоръки помагат на потребителите да открият продукти или съдържание, които иначе може да не срещнат. Това прави механизмите за препоръки голяма част от уеб сайтове и услуги като Facebook, YouTube, Amazon и др.
Двигателите за препоръки работят идеално по един от двата начина. Той може да разчита на свойствата на елементите, които харесва потребител, които се анализират, за да се определи какво друго може да хареса на потребителя; или може да разчита на харесванията и нехаресванията на други потребители, които механизмът за препоръки след това използва, за да изчисли индекс на сходство между потребителите и да им препоръча съответно елементи. Възможно е също така да се комбинират и двата метода, за да се изгради много по-стабилен механизъм за препоръки. Въпреки това, както и всички други проблеми, свързани с информацията, от съществено значение е да изберете алгоритъм, който е подходящ за проблема се адресира.
В този урок ще ви преведем през процеса на изграждане на механизъм за препоръки, който е съвместен и базиран на паметта. Този механизъм за препоръки ще препоръча на потребителите филми въз основа на това, което им харесва и не, и ще функционира като втория пример, който беше споменат преди. За този проект ще използваме основни операции за набор, малко математика и Node.js / CoffeeScript. Може да се намери целия изходен код, свързан с този урок тук .
за какво се използва docx
Преди да приложим механизъм за препоръки, базиран на съвместна памет, първо трябва да разберем основната идея зад такава система. За този механизъм всеки елемент и всеки потребител не са нищо друго освен идентификатори. Следователно, няма да вземем под внимание никакви други атрибути на филм (например актьорски състав, режисьор, жанр и др.), Докато генерираме препоръки. Сходството между двама потребители се представя чрез десетично число между -1,0 и 1,0. Ще наречем това число индекс на сходство. И накрая, възможността потребителят да хареса филм ще бъде представена с помощта на друго десетично число между -1,0 и 1,0. Сега, когато моделирахме света около тази система, използвайки прости термини, можем да освободим шепа елегантни математически уравнения, за да определим връзката между тези идентификатори и числа.
В нашия алгоритъм за препоръки ще поддържаме редица набори. Всеки потребител ще има два комплекта: набор от филми, които потребителят харесва, и набор от филми, които потребителят не харесва. Всеки филм ще има и два комплекта, свързани с него: набор от потребители, които са харесали филма, и набор от потребители, които не са харесали филма. По време на етапите, на които се генерират препоръки, ще бъдат изготвени редица набори - най-вече обединения или пресечни точки на останалите набори. Ще имаме и подредени списъци с предложения и подобни потребители за всеки потребител.
За да изчислим индекса на сходство, ще използваме вариация на формулата на индекса Джакар. Първоначално известна като „коефициент на общност“ (създаден от Пол Джакар), формулата сравнява два набора и създава проста десетична статистика между 0 и 1,0:
Формулата включва разделяне на броя на общите елементи във всеки набор от броя на всички елементи (броени само веднъж) и в двата набора. Индексът на Джакард от два еднакви множества винаги ще бъде 1, докато индексът на Джакард от два набора без общи елементи винаги ще дава 0. Сега, когато знаем как да сравняваме два набора, нека помислим за стратегия, която можем да използваме за сравняване на две потребители. Както беше обсъдено по-рано, потребителите, от гледна точка на системата, са три неща: идентификатор, набор от харесвани филми и набор от нехаресвани филми. Ако трябваше да дефинираме индекса за сходство на нашите потребители само въз основа на множеството от харесваните им филми, бихме могли директно да използваме формулата на индекса Jaccard:
Тук U1 и U2 са двамата потребители, които сравняваме, а L1 и L2 са групите филми, които U1 и U2 са харесали, съответно. Сега, ако се замислите, двама потребители, които харесват едни и същи филми, са сходни, тогава двама потребители, които не харесват едни и същи филми, също трябва да са подобни. Тук малко модифицираме уравнението:
Вместо просто да разгледаме често срещаните харесвания в числителя на формулата, сега добавяме и броя на често срещаните нехаресвания. В знаменателя вземаме броя на всички елементи, които или потребителят е харесал или не. Сега, след като разгледахме харесванията и нехаресванията по независим начин, трябва да помислим и за случая, когато двама потребители са полярни противоположности в своите предпочитания. Индексът на сходство на двама потребители, при които единият харесва филм, а другият не харесва, не трябва да бъде 0:
Това е една дълга формула! Но е просто, обещавам. Подобна е на предишната ни формула с малка разлика в числителя. Сега изваждаме броя на противоречивите харесвания и нехаресвания на двамата потребители от броя на техните общи харесвания и нехаресвания. Това кара формулата за индекс на сходство да има диапазон от стойности между -1,0 и 1,0. Двама потребители с идентични вкусове ще имат индекс на сходство 1,0, докато двама потребители с напълно противоречащи си вкусове във филмите ще имат индекс на сходство -1,0.
Сега, след като знаем как да сравняваме двама потребители въз основа на техния вкус във филмите, трябва да проучим още една формула, преди да започнем да прилагаме нашия алгоритъм на препоръчания двигател:
Нека разбием това уравнение малко. Какво имаме предвид под P(U,M)
е възможността за потребител U
харесване на филма M
. ZL
и ZD
са сумата от индексите на сходство на потребител U
с всички потребители, които са харесали или не са харесали филма, съответно M
|ML|+|MD|
представлява общия брой потребители, които са харесали или не са харесали филма M
Резултатът P(U,M)
произвежда число между -1,0 и 1,0.
Това е всичко. В следващия раздел можем да използваме тези формули, за да започнем да прилагаме нашия механизъм за препоръки, базиран на съвместна памет.
Ще изградим този механизъм за препоръки като много просто приложение Node.js. Също така ще има много малко работа върху предния край, най-вече някои HTML страници и формуляри (ще използваме Bootstrap, за да направим страниците да изглеждат добре). От страна на сървъра ще използваме CoffeeScript. Приложението ще има няколко GET и POST маршрута. Въпреки че ще имаме понятие за потребители в приложението, няма да имаме сложен механизъм за регистрация / влизане. За устойчивост ще използваме пакета Bourne, наличен чрез NPM, който позволява на приложението да съхранява данни в обикновени JSON файлове и да изпълнява основни заявки към тях. Ще използваме Express.js, за да улесним процеса на управление на маршрутите и манипулаторите.
В този момент, ако сте нов за Разработка на Node.js , може да искате да клонирате Хранилище на GitHub така че да е по-лесно да следвате този урок. Както при всеки друг проект на Node.js, ще започнем от създаване на файл package.json и инсталиране на набор от пакети за зависимости, необходими за този проект. Ако използвате клонираното хранилище, файлът package.json вече трябва да е там, откъдето инсталирането на зависимостите ще изисква да изпълните „$ npm install“. Това ще инсталира всички пакети, изброени във файла package.json.
Пакетите Node.js, които са ни необходими за този проект, са:
Ще изградим механизма за препоръки, като разделим всички съответни методи на четири отделни класове CoffeeScript, всеки от които ще се съхранява в „lib / engine“: Engine, Rater, Similars и Suggestions. Класът Engine ще отговаря за предоставянето на прост API за механизма за препоръки и ще обвърже останалите три класа заедно. Rater ще отговаря за проследяването на харесвания и нехаресвания (като два отделни екземпляра от класа Rater). Подобни и предложения ще отговарят за определянето и проследяването на подобни потребители и препоръчаните елементи за потребителите, съответно.
Нека първо започнем с нашия клас Raters. Това е просто:
class Rater constructor: (@engine, @kind) -> add: (user, item, done) -> remove: (user, item, done) -> itemsByUser: (user, done) -> usersByItem: (item, done) ->
Както бе посочено по-рано в този урок, ще имаме един екземпляр на Rater за харесвания и друг за нехаресвания. За да запишем, че даден потребител харесва елемент, ще го предадем на „Rater # add ()“. По същия начин, за да премахнем рейтинга, ще ги предадем на „Rater # remove ()“.
Тъй като използваме Bourne като решение за база данни без сървър, ще съхраним тези оценки във файл с име „./db-#{@kind}.json“, където видът е „харесвания“ или „нехаресвания“. Ще отворим базата данни в конструктора на екземпляра Rater:
constructor: (@engine, @kind) -> @db = new Bourne './db-#{@kind}.json'
Това ще направи добавянето на записи за оценка толкова просто, колкото извикването на метод на базата данни на Bourne вътре в нашия метод „Rater # add ()“:
@db.insert user: user, item: item, (err) =>
И е подобно да ги премахнете („db.delete“ вместо „db.insert“). Преди обаче да добавим или премахнем нещо, трябва да се уверим, че то вече не съществува в базата данни. В идеалния случай, с истинска база данни, бихме могли да го направим като една операция. С Борн първо трябва да направим ръчна проверка; и след като вмъкването или изтриването приключи, трябва да сме сигурни, че сме преизчислили индексите за сходство за този потребител и след това да генерираме набор от нови предложения. Методите 'Rater # add ()' и 'Rater # remove ()' ще изглеждат по следния начин:
add: (user, item, done) -> @db.find user: user, item: item, (err, res) => if res.length > 0 return done() @db.insert user: user, item: item, (err) => async.series [ (done) => @engine.similars.update user, done (done) => @engine.suggestions.update user, done ], done remove: (user, item, done) -> @db.delete user: user, item: item, (err) => async.series [ (done) => @engine.similars.update user, done (done) => @engine.suggestions.update user, done ], done
За краткост ще пропуснем частите, където проверяваме за грешки. Това може да е разумно нещо в една статия, но не е оправдание за игнориране на грешки в реалния код.
Другите два метода, „Rater # itemsByUser ()“ и „Rater # usersByItem ()“ от този клас, ще включват извършване на това, което предполагат имената им - търсене на елементи, оценени съответно от потребител и потребители, които са оценили елемент. Например, когато Rater е създаден с kind = “likes”
, „Rater # itemsByUser ()“ ще намери всички елементи, които потребителят е оценил.
Преминаване към следващия ни клас: Подобни. Този клас ще ни помогне да изчислим и да следим индексите на сходство между потребителите. Както беше обсъдено по-горе, изчисляването на сходството между двама потребители включва анализ на наборите от елементи, които харесват и не харесват. За целта ще разчитаме на екземплярите на Rater, за да извлечем наборите от съответни елементи и след това да определим индекса на сходство за определени двойки потребители, използвайки формулата за индекс на сходство.
как да си направим google чаша
Точно като нашия предишен клас, Rater, ние ще поставим всичко в база данни на Bourne, наречена „./db-similars.json“, която ще отворим в конструктора на Rater. Класът ще има метод “Similars # byUser ()”, който ще ни позволи да търсим потребители, подобни на даден потребител чрез просто търсене в базата данни:
@db.findOne user: user, (err, {others}) =>
Най-важният метод от този клас обаче е “Similars # update ()”, който работи, като взема потребител и изчислява списък на други потребители, които са подобни, и съхранява списъка в базата данни, заедно с техните индекси на сходство. Започва с намиране на харесвания и антипатии на потребителя:
async.auto userLikes: (done) => @engine.likes.itemsByUser user, done userDislikes: (done) => @engine.dislikes.itemsByUser user, done , (err, {userLikes, userDislikes}) => items = _.flatten([userLikes, userDislikes])
Също така намираме всички потребители, които са оценили тези елементи:
async.map items, (item, done) => async.map [ @engine.likes @engine.dislikes ], (rater, done) => rater.usersByItem item, done , done , (err, others) =>
След това за всеки от тези други потребители изчисляваме индекса на сходство и съхраняваме всичко в базата данни:
async.map others, (other, done) => async.auto otherLikes: (done) => @engine.likes.itemsByUser other, done otherDislikes: (done) => @engine.dislikes.itemsByUser other, done , (err, {otherLikes, otherDislikes}) => done null, user: other similarity: (_.intersection(userLikes, otherLikes).length+_.intersection(userDislikes, otherDislikes).length-_.intersection(userLikes, otherDislikes).length-_.intersection(userDislikes, otherLikes).length) / _.union(userLikes, otherLikes, userDislikes, otherDislikes).length , (err, others) => @db.insert user: user others: others , done
В горния фрагмент ще забележите, че имаме израз, идентичен по своята същност на нашата формула за индекс на сходство, вариант на формулата на индекса на Джакар.
Следващият ни клас, Предложения, е мястото, където се извършват всички прогнози. Подобно на класа Similars, ние разчитаме на друга база данни на Bourne, наречена “./db-suggestions.json”, отворена вътре в конструктора.
Класът ще има метод “Suggestions # forUser ()” за търсене на изчислени предложения за дадения потребител:
forUser: (user, done) -> @db.findOne user: user, (err, {suggestions}={suggestion: []}) -> done null, suggestions
Методът, който ще изчисли тези резултати, е “Suggestions # update ()”. Този метод, като „Similars # update ()“, ще вземе потребител като аргумент. Методът започва с изброяване на всички потребители, подобни на дадения потребител, и всички елементи, които даден потребител не е оценил:
@engine.similars.byUser user, (err, others) => async.auto likes: (done) => @engine.likes.itemsByUser user, done dislikes: (done) => @engine.dislikes.itemsByUser user, done items: (done) => async.map others, (other, done) => async.map [ @engine.likes @engine.dislikes ], (rater, done) => rater.itemsByUser other.user, done , done , done , (err, {likes, dislikes, items}) => items = _.difference _.unique(_.flatten items), likes, dislikes
След като изброим всички останали потребители и неоценените елементи, можем да започнем да изчисляваме нов набор от препоръки, като премахваме всички предишни набори от препоръки, итерираме всеки елемент и изчисляваме възможността потребителят да го хареса въз основа на наличната информация:
@db.delete user: user, (err) => async.map items, (item, done) => async.auto likers: (done) => @engine.likes.usersByItem item, done dislikers: (done) => @engine.dislikes.usersByItem item, done , (err, {likers, dislikers}) => numerator = 0 for other in _.without _.flatten([likers, dislikers]), user other = _.findWhere(others, user: other) if other? numerator += other.similarity done null, item: item weight: numerator / _.union(likers, dislikers).length , (err, suggestions) =>
След като приключим, го запазваме обратно в базата данни:
@db.insert user: user suggestions: suggestions , done
В класа на Engine ние свързваме всичко в изчистена API-подобна структура за лесен достъп от външния свят:
class Engine constructor: -> @likes = new Rater @, 'likes' @dislikes = new Rater @, 'dislikes' @similars = new Similars @ @suggestions = new Suggestions @
След като създадем екземпляр на обект на Engine:
e = new Engine
Можем лесно да добавяме или премахваме харесвания и нехаресвания:
e.likes.add user, item, (err) -> e.dislikes.add user, item, (err) ->
Също така можем да започнем да актуализираме индекси и предложения за сходство на потребителите:
e.similars.update user, (err) -> e.suggestions.update user, (err) ->
И накрая, важно е да експортирате този клас Engine (и всички останали класове) от съответните им “.coffee” файлове:
module.exports = Engine
След това експортирайте Engine от пакета, като създадете файл „index.coffee“ с един ред:
module.exports = require './engine'
За да можем да използваме алгоритъма на механизма за препоръки в този урок, искаме да предоставим прост потребителски интерфейс през мрежата. За да направим това, ние създаваме Express приложение във нашия файл „web.iced“ и обработваме няколко маршрута:
movies = require './data/movies.json' Engine = require './lib/engine' e = new Eengine app = express() app.set 'views', '#{__dirname}/views' app.set 'view engine', 'jade' app.route('/refresh') .post(({query}, res, next) -> async.series [ (done) => e.similars.update query.user, done (done) => e.suggestions.update query.user, done ], (err) => res.redirect '/?user=#{query.user}' ) app.route('/like') .post(({query}, res, next) -> if query.unset is 'yes' e.likes.remove query.user, query.movie, (err) => res.redirect '/?user=#{query.user}' else e.dislikes.remove query.user, query.movie, (err) => e.likes.add query.user, query.movie, (err) => if err? return next err res.redirect '/?user=#{query.user}' ) app.route('/dislike') .post(({query}, res, next) -> if query.unset is 'yes' e.dislikes.remove query.user, query.movie, (err) => res.redirect '/?user=#{query.user}' else e.likes.remove query.user, query.movie, (err) => e.dislikes.add query.user, query.movie, (err) => res.redirect '/?user=#{query.user}' ) app.route('/') .get(({query}, res, next) -> async.auto likes: (done) => e.likes.itemsByUser query.user, done dislikes: (done) => e.dislikes.itemsByUser query.user, done suggestions: (done) => e.suggestions.forUser query.user, (err, suggestions) => done null, _.map _.sortBy(suggestions, (suggestion) -> -suggestion.weight), (suggestion) => _.findWhere movies, id: suggestion.item , (err, {likes, dislikes, suggestions}) => res.render 'index', movies: movies user: query.user likes: likes dislikes: dislikes suggestions: suggestions[...4] )
В рамките на приложението ние обработваме четири маршрута. Маршрутът на индекса „/“ е мястото, където обслужваме HTML от предния край, като изобразяваме шаблон на Jade. Генерирането на шаблона изисква списък с филми, потребителското име на текущия потребител, харесванията и нехаресванията на потребителя и четирите най-добри предложения за потребителя. Изходният код на шаблона Jade е оставен извън статията, но е достъпен в Хранилище на GitHub .
Маршрутите „/ харесване“ и „/ нехаресване“ са мястото, където приемаме POST заявки за записване на харесвания и нехаресвания на потребителя. И двата маршрута добавят оценка, като първо премахват всяка противоречива оценка, ако е необходимо. Например, потребител, харесал нещо, което преди не е харесал, ще накара манипулаторът да премахне първо оценката „нехаресване“. Тези маршрути също позволяват на потребителя да „различава“ или „премахва нехаресването“ на елемент, ако желаете.
И накрая, маршрутът „/ refresh“ позволява на потребителя да регенерира своя набор от препоръки при поискване. Въпреки това, това действие се извършва автоматично, когато потребителят прави някаква оценка на даден елемент.
Ако сте се опитали да приложите това приложение от нулата, като следвате тази статия, ще трябва да извършите една последна стъпка, преди да можете да го тествате. Ще трябва да създадете „.json“ файл на „data / movies.json“ и да го попълните с някои филмови данни по следния начин:
[ { 'id': '1', 'name': 'Transformers: Age of Extinction', 'thumb': { 'url': '//upload.wikimedia.org/wikipedia/en/7/7f/Inception_ver3.jpg' } }, // … ]
Може да искате да копирате наличния в Хранилище на GitHub , който е предварително попълнен с шепа имена на филми и URL адреси на миниатюри.
След като целият изходен код е готов и свързан заедно, стартирането на процеса на сървъра изисква да бъде извикана следната команда:
$ npm start
Ако приемем, че всичко е минало гладко, трябва да видите следния текст да се появи на терминала:
Listening on 5000
Тъй като не сме внедрили нито една истинска система за удостоверяване на потребителя, прототипното приложение разчита само на потребителско име, избрано след посещение на „http: // localhost: 5000“. След като въведете потребителско име и формулярът бъде изпратен, трябва да бъдете отведени на друга страница с два раздела: „Препоръчани филми“ и „Всички филми“. Тъй като ни липсва най-важният елемент на базиран на паметта механизъм за препоръки (данни), няма да можем да препоръчаме филми на този нов потребител.
В този момент трябва да отворите друг прозорец на браузъра за „http: // localhost: 5000“ и да влезете като различен потребител там. Харесвайте и не харесвайте някои филми като този втори потребител. Върнете се в прозореца на браузъра на първия потребител и оценете и някои филми. Уверете се, че сте оценили поне няколко общи филма и за двамата потребители. Трябва незабавно да започнете да виждате препоръки.
В този урок за алгоритъм това, което изградихме, е механизъм за препоръчване на прототип. Със сигурност има начини за подобряване на този двигател. Този раздел ще засегне накратко някои области, в които подобренията са от съществено значение, за да се използва в голям мащаб. Въпреки това, в случаите, когато се изисква мащабируемост, стабилност и други подобни свойства, винаги трябва да прибягвате до използване на добро изпитано във времето решение. Подобно на останалата част от статията, идеята и тук е да се даде известна представа за това как работи механизмът за препоръки. Вместо да обсъждаме очевидните недостатъци на настоящия метод (като състоянието на расата в някои от методите, които сме внедрили), подобренията ще бъдат обсъдени на по-високо ниво.
Едно много очевидно подобрение тук е да се използва реална база данни, вместо нашето базирано на файлове решение. Решението, базирано на файлове, може да работи добре в прототип в малък мащаб, но изобщо не е разумен избор за реална употреба. Един от вариантите сред много е Redis. Redis е бърз и има специални възможности които са полезни при работа с подобни на набор от структури данни.
глобалните приходи от продажби на цифрова музика са достигнали над 5 милиарда щатски долара.
Друг проблем, който можем просто да заобиколим, е фактът, че изчисляваме нови препоръки всеки път, когато потребителят прави или променя рейтинга си за филми. Вместо да правим преизчисления в движение в реално време, трябва да поставим на опашка тези заявки за актуализация на препоръки за потребителите и да ги извършим зад сцената - може би да зададем интервал за опресняване по време.
Освен тези „технически“ решения, има и някои стратегически решения, които могат да бъдат направени за подобряване на препоръките. С нарастването на броя на артикулите и потребителите генерирането на препоръки ще става все по-скъпо (по отношение на времето и системните ресурси). Възможно е да направите това по-бързо, като изберете само подмножество потребители, от които да генерирате препоръки, вместо да обработвате цялата база данни всеки път. Например, ако това беше механизъм за препоръки за ресторанти, можете да ограничите сходния потребителски набор да съдържа само тези потребители, които живеят в същия град или щат.
Други подобрения могат да включват хибриден подход, при който се генерират препоръки въз основа както на съвместно филтриране, така и на филтриране въз основа на съдържание. Това би било особено добре при съдържание като филми, където свойствата на съдържанието са добре дефинирани. Netflix например поема по този път, като препоръчва филми, базирани както на дейностите на други потребители, така и на атрибутите на филмите.
Базираните на паметта алгоритми за съвместни препоръки на двигателя могат да бъдат доста мощно нещо. Този, с който експериментирахме в тази статия, може да е примитивен, но е и прост: лесен за разбиране и лесен за изграждане. Може да е далеч от перфектни, но стабилните имплементации на препоръчителни механизми, като препоръчително, са изградени върху подобни фундаментални идеи.
Подобно на повечето други проблеми на компютърните науки, които включват много данни, получаването на правилни препоръки е много важно избор на правилния алгоритъм и подходящи атрибути на съдържанието, върху което да се работи. Надявам се тази статия да ви даде представа какво се случва в механизма за препоръки, базиран на съвместна памет, когато го използвате.