Предсказание популярности статьи еще до ее написания

Всем привет! Захотелось мне запилить приложение, которое бы предсказывало популярность статьи на Либератуме еще до ее написания. Ведь было бы круто не писать статьи, которые в будущем не станут популярными и не привлекут читателя. И писать статьи, которые привлекут читателя? Как вам такой план?

Web-приложение, классификатор текстов на Либератуме

Гугление вопроса показало, что хипстеры и прочие наркоманы используют для этих целей нейронные сети. Тогда как есть простой и чертовски эффективный, дедовский способ классификации текстов — наивный байесовский классификатор.

Действует НБК просто: на вход подается текст (в данном случае заголовок статьи) и класс документа (в данном случае shit или cool). «Обучая» классификатор, мы строим таблицу вероятностей встречи ключевого слова с учетом того, к какому классу относится документ. После того, как таблица будет построена, на вход классификатора мы будем подавать произвольные заголовки (и вы сами сможете в этом поучаствовать), а на выходе получим класс документа: shit или cool.

Единственный серьезный вопрос: на каких данных обучать классификатор? Для этого я экспортировал заголовки статей с Либератума за 10 лет. Около 11 тыс. штук.

Экспорт CSV данных

Сами заголовки, понятное дело, ценности не представляют, нужно каждый соотнести с одним из классов популярности. Для этого я экспортировал и число просмотров каждой статьи.

Число просмотров и комментариев каждой статьи на Либератуме

Трудным вопросом стало выяснение того, что считать популярным, а что нет. Я долго чесал яйца затылок, разглядывая гистограмму постов, накладывал на нее медиану и среднее, боролся с выбросами с помощью логарифмов и т.п.

Вот общий график популярности постов:

Популярность постов

Красная линия — медиана, синяя — среднее арифметическое.

А вот гистограмма:

Гистограмма популярности статей на сайте, Либератум

Что считать границей популярных постов и лажовых? Я даже пытался вводить больше классов и использовать классификатор K-Means. Вот такое разбиение было предложено:

K-Means классификатор для оценки популярности статей на Либератуме

И такое деление на классы (норм, огонь, бестселлер, кал) показало очень плохие результаты. Позже я понял почему, но долго объяснять вам, не знающим нюансы байесовского классификатора.

А лучший результат показало деление по среднему арифметическому.

Медианное значение и среднее арифметическое

2550 просмотров на статью. Ниже этого значения — shit, больше или равно — cool.

Теперь обучим классификатор. НБК имеет очень простую формулу и запилить классификатор можно самому. Делать этого я конечно же не стану. Возьму готовый.

Обучение:


const fs = require('fs')
const papa = require('papaparse')
const bayes = require('bayes')
const _ = require('lodash')

var classifier = bayes()

var train = (resolve, reject) => {
let f = fs.readFileSync(‘../data/liberpop.csv’, ‘utf8’)
papa.parse(f, {
delimiter: ‘,’,
complete: (result) => {
_.each(result.data, (i) => {
if (i[2] != null) {
classifier.learn(i[2].toLowerCase(), i[4])
}
})
}
})
resolve(1)
}

(new Promise(train)).then(() => {
fs.writeFile(‘../data/probabilities.json’, classifier.toJson(), () => {
console.log(‘Results saved to probabilities.json’)
})
})

Библиотека PapaParse помогла распарсить CSV, а библиотека Bayes предоставила байесовский наивный классификатор. Результат обучения (JSON, ~800 Kb) сохранен в probabilities.json. Вуаля! Теперь можно подгружать probabilities.json в браузер к клиенту и юзер сможет вычислять популярность статей еще до их написания. Последний штрих (client side):


var bayes = require('bayes')
var axios = require('axios')

const PROBABILITIES_FILE = ‘https://nobleman.xyz/tools/editor/wanga/js/probabilities.json’

var classifier

document.addEventListener(‘DOMContentLoaded’, () => {
axios.get(PROBABILITIES_FILE).then((res) => {
classifier = bayes.fromJson(JSON.stringify(res.data))
var title = document.getElementById(‘title’)
var results = document.getElementById(‘results’)
title.addEventListener(‘keydown’, (e) => {
if (e.keyCode == 13) {
var t = title.value
let c = classifier.categorize(t.toLowerCase())
title.value = »
results.innerHTML = » + t + » + c + » + results.innerHTML;
}
})
}).catch((err) => {
console.log(‘Axios error: ‘ + err)
})
})

В фоне подгружается probabilities.json, подключается к классификатору, всё это дело вешается на окно ввода. Готово. Можно пользоваться.

Web-приложение, классификатор текстов на Либератуме

Ссылка на рабочее web-приложение: https://nobleman.xyz/tools/editor/wanga/