У меня есть своя группа «Вконтакте», и мне стало интересно, можно ли спрогнозировать, будет публикация успешной или нет. Я решил написать программу, которая бы автоматически брала текст поста, проверяла его с помощью нейронной сети и публиковала только в том случае, если вероятность получить одобрение аудитории достаточно высока. Разрабатывать мы будем на Qt5 и используем Python и Keras для обучения нейросети.
Для начала в проекте на Qt в файле .pro подключим библиотеку
network, добавив строку
QT += network. Чтобы разместить пост на стене группы, выполняем код:
1 2 3 4 5 6 7 8 9 10 11 12 |
QString cit = "Hello!"; QUrl apiUrl; QString str = "https://api.vkontakte.ru/method/wall.post?owner_id=-78329950&message=" + cit + "&from_group=1&access_token=abcdef&v=5.73"; apiUrl.setUrl(str); QByteArray requestString = ""; QNetworkRequest request(apiUrl); request.setRawHeader("Content-Type", "application/octet-stream"); QNetworkAccessManager manager_vk; connect(&manager_vk, SIGNAL(finished(QNetworkReply*)), this, SLOT(slotV(QNetworkReply*))); manager_vk.post(request, requestString); |
Как видишь, мы отправляем запрос POST с адресом в переменной str. Используем метод VK API wall.post, чтобы разместить пост на стене группы. Параметр owner_id должен быть равен номеру группы, но записан со знаком минус; параметр message содержит текст поста; from_group — указатель, что пост будет размещен от имени сообщества, он равен единице; access_token — токен доступа.
Ты мог заметить, что для обработки запроса мы подключили слот slotV. Он может выглядеть так:
1 2 3 4 |
void MainWindow::slotV(QNetworkReply* r) { qDebug() << QString::fromUtf8(r->readAll()); } |
В консоли ты увидишь результат запроса: ошибку, если что-то пойдет не так, или сообщение с номером опубликованного поста.
Создание базы данных для обучения нейронной сети
Чтобы обучить нейронную сеть, нам нужна обширная база данных постов, оцененных реальными пользователями. Ты можешь взять данные из своей группы, а можешь воспользоваться данными из любой другой открытой группы. Нам нужно собрать файл с отметками о количестве лайков для каждого поста. Для этого напишем программу.
Чтобы извлечь каждый пост, отправляем запрос GET:
1 |
QString str = "https://api.vk.com/method/wall.getById?posts=-78329950_" + QString::number(cnt) + "&v=5.84&access_token=abcdef"; |
Мы используем метод VK API wall.getById: извлекаем пост со стены группы по его номеру.
Параметр posts содержит уникальный идентификатор поста (здесь — -78329950_123), который состоит из идентификатора группы со знаком минус и порядкового номера поста, разделенных знаком _. Порядковый номер поста содержится в переменной cnt. Параметр access_token — это токен доступа к группе.
Отправим этот запрос столько раз, сколько постов нам нужно извлечь, изменяя переменную cnt в соответствии с порядковым номером поста. Получим код HTML, который будет содержать текст поста, количество лайков и другую информацию. Распарсим каждый ответ, чтобы извлечь из него текст и количество лайков. Сохраним в текстовый файл данные, которые мы получили.
1 2 3 4 5 6 7 8 9 10 |
номер поста #1 текст поста #1 количество лайков #1 номер поста #2 текст поста #2 количество лайков #2 ... номер поста #n текст поста #n количество лайков #n |
Теперь, когда все посты записаны в файл, мы готовы синтезировать нейронную сеть и обучать ее.
Keras для работы с нейронными сетями
Нам понадобится Keras — библиотека для работы с нейронными сетями.
Есть замечательная книга — «Глубокое обучение на Python» Франсуа Шолле. По ней ты можешь освоить теорию, ознакомиться с примерами решения задач от самых простых до весьма сложных, таких как сверточные сети и генерация изображений.
Для начала выполним ряд стандартных действий и установим Python:
1 2 3 |
$ sudo apt-get update $ sudo apt-get upgrade $ sudo apt-get install python-pip python-dev python-setuptools |
Затем установим пакеты научных вычислений для Python:
1 2 3 |
$ sudo apt-get install build-essential cmake git unzip pkg-config libopenblas-dev liblapack-dev $ sudo apt-get install python-numpy python-scipy python-matplotlib python-yaml $ sudo apt-get install libhdf5-serial-dev python-h5py |
Поставим TensorFlow:
1 |
$ sudo pip install tensorflow |
И саму библиотеку Keras:
1 |
$ sudo pip install keras |
Можно установить Keras и из репозитория на GitHub. В этом случае ты получишь доступ к папке keras/examples с примерами сценариев.
1 2 3 |
$ git clone https://github.com/fchollet/keras $ cd keras $ sudo python setup.py install |
Чтобы проверить, что все установилось, попробуй запустить сценарий Keras.
1 |
python examples/mnist_cnn.py |
Для выполнения этого примера может потребоваться несколько минут.
Обучение нейронной сети
Основу своей нейронной сети я выбрал из библиотек Keras — нейронная сеть предсказывает, какой отзыв получит рецензия на фильм: положительный или отрицательный. В нашем случае сеть будет определять, получит пост лайки или нет. Начнем с подключения всех необходимых библиотек и модулей.
1 2 3 4 5 6 7 8 9 10 11 12 13 |
#! /usr/bin/env python # -*- coding: utf-8 -*- from keras.preprocessing.text import Tokenizer from keras.preprocessing import sequence from keras.models import Sequential, load_model from keras.layers import Dense, Dropout, Activation from keras.layers import Embedding from keras.layers import Conv1D, GlobalMaxPooling1D import numpy as np import pickle |
Объявим два массива: string_list и mark_list. В первом будут храниться тексты постов, а во втором ноль, если у соответствующего поста не было лайков, и единица, если были. Прочитаем все посты из созданного ранее файла базы данных.
1 2 3 4 5 6 7 8 9 10 11 12 13 |
string_list = [] mark_list = [] handle = open("vkbase.txt", "r") for i in range(20000): s = handle.readline() s = handle.readline() string_list.append(s) s = handle.readline() l = int(s) if l > 0: mark_list.append(1) else: mark_list.append(0) |
Создадим и подготовим токенайзер:
1 2 |
tokenizer = Tokenizer(num_words=15000) tokenizer.fit_on_texts(string_list) |
Токенайзер создает таблицу, в которой каждому слову из нашего массива постов присваивается уникальное число. Максимальное число в моем случае — 15000.
Сохраним таблицу токенайзера в файл, чтобы пользоваться ей в дальнейшем:
1 2 |
with open('tokenizer.pickle', 'wb') as hand: pickle.dump(tokenizer, hand, protocol=pickle.HIGHEST_PROTOCOL) |
А теперь преобразуем наш массив постов в массив чисел в соответствии с таблицей токенайзера:
1 |
sequences = tokenizer.texts_to_sequences(string_list) |
Тут я бы хотел отметить, что мы прочитали не все посты из файла, а только те, которые нам понадобятся для обучения нейронной сети. Давай прочитаем оставшиеся 3000 постов, которые будут использоваться для проверки работоспособности нашей нейросети, и также преобразуем их в массивы чисел.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
string_list_test = [] mark_list_test = [] for i in range(3000): s = handle.readline() s = handle.readline() string_list_test.append(s) s = handle.readline() l = int(s) if l > 0: mark_list_test.append(1) else: mark_list_test.append(0) sequences_test = tokenizer.texts_to_sequences(string_list_test) |
Далее наши массивы надо привести к формату, удобному для понимания нейронной сетью. Это значит, что число слов в каждом посте должно быть одинаковым. Зададим его равным 400. Если в каком-нибудь посте будет меньше слов, то оставшиеся числа заполнятся нулями:
1 2 |
x_train = sequence.pad_sequences(sequences, maxlen=400) x_test = sequence.pad_sequences(sequences_test, maxlen=400) |
На данный момент все подготовительные мероприятия завершены, и мы можем синтезировать нейронную сеть.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 |
print('Build model...') model = Sequential() # We start off with an efficient embedding layer which maps # our vocab indices into embedding_dims dimensions model.add(Embedding(20000, 50, input_length=400)) model.add(Dropout(0.2)) # We add a Convolution1D, which will learn filters # word group filters of size filter_length: model.add(Conv1D(250, 3, padding='valid', activation='relu', strides=1)) # We use max pooling: model.add(GlobalMaxPooling1D()) # We add a vanilla hidden layer: model.add(Dense(250)) model.add(Dropout(0.2)) model.add(Activation('relu')) # We project onto a single unit output layer, and squash it with a sigmoid: model.add(Dense(1)) model.add(Activation('sigmoid')) |
Этот код я полностью взял из примера и заменил лишь размерности данных.
Давай скомпилируем нашу нейронную сеть.
1 2 3 |
model.compile(loss='binary_crossentropy', optimizer='adam', metrics=['accuracy']) |
А теперь напишем код, который запустит тренировку сети и сохранит результат в файл, чтобы он не потерялся.
1 2 |
model.fit(x_train, mark_list, batch_size=32, epochs=15, validation_data=(x_test, mark_list_test)) model.save("vkbase.h5") |
Чтобы начать обучение, нужно запустить наш скрипт на исполнение из консоли. В терминале должна появиться бегущая строка.
1 |
14784/20000 [=====================>........] - ETA: 8s - loss: 0.5232 - acc: 0.7835 |
Процесс может занять до десяти минут, придется подождать. У меня точность обучения составила 62%. Это говорит о том, что в данных была найдена закономерность и сеть можно использовать по назначению. Если бы результат составил 50% или меньше, это значило бы, что закономерностей не найдено и нейронная сеть просто угадывает исход с той же вероятностью.
Давай проверим, как работает наша сеть.
1 2 3 4 5 6 7 8 9 10 11 12 |
network = load_model("vkbase.h5") strin = ["А ты знаешь, мне сегодня грустно. Люди разучились быть людьми, В этом мире стало слишком пусто... Слов не надо... Просто обними... Наталия Коденцова"] #loading tokenizer with open('tokenizer.pickle', 'rb') as handle: tokenizer = pickle.load(handle) seq = tokenizer.texts_to_sequences(strin) x_seq = sequence.pad_sequences(seq, maxlen=400) prediction = network.predict(np.array(x_seq), verbose=1) print(prediction) |
Этот скрипт показывает, с какой вероятностью данная цитата получит лайк. Им мы и будем пользоваться в дальнейшем.
Внедрение нейронной сети на Python в проект на С++
Наша нейронная сеть обучена и готова к использованию. Теперь нам надо внедрить ее в проект на С++ на платформе Qt5, который будет постить отобранные посты в ВК.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
#include "/usr/include/python2.7/Python.h" Py_Initialize(); PyObject *moduleMainString = PyString_FromString("__main__"); PyObject *moduleMain = PyImport_Import(moduleMainString); PyRun_SimpleString( "import pickle\n" "import numpy as np\n" "from keras.preprocessing.text import Tokenizer\n" "from keras.preprocessing import sequence\n" "from keras.models import Sequential, load_model \n" "def neuron(text): \n" " network = load_model(\"/home/alex/Projects/vkbase/vkbase.h5\") \n" " strin = [text]\n" " #loading tokenizer \n" " with open(\"/home/alex/Projects/vkbase/tokenizer.pickle\", \"rb\") as handle: \n" " tokenizer = pickle.load(handle) \n" " seq = tokenizer.texts_to_sequences(strin) \n" " x_seq = sequence.pad_sequences(seq, maxlen=400) \n" " prediction = network.predict(np.array(x_seq), verbose=1) \n" " return prediction \n" ); |
Тебе нужно только прописать свои пути к файлам vkbase.h5 и tokenizer.pickle.
Скрипт, который мы внедрили в проект, — функция под названием neuron, которая принимает параметр text и возвращает вероятность в виде числа prediction. Опишем доступ к нашей функции и аргумент, который мы будем в нее отдавать.
1 2 3 |
PyObject *func = PyObject_GetAttrString(moduleMain, "neuron"); char [] cit = "Hello!"; PyObject *args = PyTuple_Pack(1, PyString_FromString(cit)); |
Здесь переменная cit — это пост, который надо проверить. Теперь вызовем нашу функцию с постом в качестве аргумента и получим результат.
1 2 |
PyObject *result = PyObject_CallObject(func, args); double koef = PyFloat_AsDouble(result); |
Переменная koef и есть наш результат, вероятность того, что пост получит лайки. Теперь мы можем проверить его: если он больше 0.5, мы можем размещать пост в сети, если меньше — лучше взять другой.
Таким образом мы можем реализовать автопостинг: завести таймер (например, на один час), по его срабатыванию брать пост из банка постов, проверять на пригодность с помощью нейронной сети и публиковать в случае успеха.
Итого
После внедрения описанного проекта в реальной группе ВК я увидел результат довольно скоро. Количество лайков не сильно изменилось, но количество подписчиков продолжает расти, так что этот проект оказался не только интересным, но и полезным. Надеюсь, не для меня одного.