Использование SciPy curve_fit для прогнозирования окончательного результата - PullRequest
6 голосов
/ 16 мая 2019

У меня есть сообщение, и мне нужно предсказать окончательную оценку как можно ближе.

Очевидно, использование curve_fit должно сработать, хотя я не совсем понимаю, как его использовать.

У меня есть два известных значения, которые я собираю через 2 минуты после публикации сообщения.

Это количество комментариев, обозначенное n_comments, и количество голосов, обозначаемое n_votes.

Через час я снова проверяю сообщение и получаю значение final_score (сумма всех голосов), которое я и хочу предсказать.

Я просмотрел различные примеры в Интернете, но все они используют несколько точек данных (у меня их всего 2), а также моя исходная точка данных содержит больше информации (n_votes и n_comments), поскольку я обнаружил, что без других вас не может точно предсказать счет.

Для использования curve_fit вам нужна функция. Моя выглядит так:

def func(datapoint,k,t,s):
    return ((datapoint[0]*k+datapoint[1]*t)*60*datapoint[2])*s

И образец точки данных выглядит так:

[n_votes, n_comments, hour] 

Это сломанный беспорядок моей попытки, и результат выглядит совсем не так.

 import numpy as np
 import matplotlib.pyplot as plt
 from scipy.optimize import curve_fit


 initial_votes_list = [3, 1, 2, 1, 0]
 initial_comment_list = [0, 3, 0, 1, 64]
 final_score_list = [26,12,13,14,229]

 # Those lists contain data about multiple posts; I want to predict one at a time, passing the parameters to the next.

 def func(x,k,t,s):
     return ((x[0]*k+x[1]*t)*60*x[2])*s

 x = np.array([3, 0, 1])
 y = np.array([26 ,0 ,2])
 #X = [[a,b,c] for a,b,c in zip(i_votes_list,i_comment_list,[i for i in range(len(i_votes_list))])]


 popt, pcov = curve_fit(func, x, y)

 plt.plot(x, [ 1 , func(x, *popt), 2], 'g--',
          label='fit: a=%5.3f, b=%5.3f, c=%5.3f' % tuple(popt))

 plt.xlabel('Time')
 plt.ylabel('Score')
 plt.legend()
 plt.show()

График должен отображать начальную / конечную оценку и текущий прогноз.

У меня тоже есть некоторые сомнения относительно этой функции. Изначально это выглядело так:

(votes_per_minute + n_comments) * 60 * hour

Но я заменил votes_per_minute только голосами. Учитывая, что я собираю эти данные через 2 минуты и у меня есть параметр, я бы сказал, что это не слишком плохо, но я действительно не знаю.

Опять же, кто гарантирует, что это лучшая возможная функция? Было бы неплохо, чтобы функция была обнаружена автоматически, но я думаю, что это территория ML ...

EDIT:

Относительно измерений: я могу получить столько, сколько захочу (каждые 15-30-60 с), хотя их нужно собирать, пока у поста возраст <3 минуты. </p>

1 Ответ

4 голосов
/ 19 мая 2019

Отказ от ответственности: Это всего лишь предложение о том, как вы можете решить эту проблему.Возможно, есть и лучшие альтернативы.

Думаю, было бы полезно принять во внимание взаимосвязь между elapsed-time-since-posting и final-score.Следующая кривая из [OC] Upvotes по времени для поста Reddit моделирует поведение final-score или total-upvotes-count во времени: enter image description here

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

Кроме того, мы знаем, что обычно количество голосов / комментариев возрастает в зависимости от времени.связь между этими элементами можно рассматривать как серию, я решил смоделировать ее как геометрическую прогрессию (вы можете рассмотреть арифметическую, если вы видите, что она лучше).Кроме того, вы должны помнить, что вы учитываете некоторые элементы дважды;Некоторые пользователи прокомментировали и проголосовали, так что вы посчитали их дважды, а некоторые могут комментировать несколько раз, но могут проголосовать только один раз.Я решил учесть, что только 70% (в коде p = 0.7) пользователей являются уникальными комментаторами, а пользователи, которые комментировали и голосовали, представляют 60% (в коде e = 1-0.6 = 0.4) от общего числа пользователей (комментаторов и пользователей, добавивших комментарий).результат этих допущений:

enter image description here

Итак, у нас есть два уравнения для моделирования баллов, чтобы вы могли объединить их и взять их среднее значение.В коде это будет выглядеть так:

import warnings 
import numpy as np
import matplotlib.pyplot as plt
from scipy.optimize import curve_fit
from mpl_toolkits.mplot3d import axes3d
# filter warnings
warnings.filterwarnings("ignore")

class Cfit: 
    def __init__(self, votes, comments, scores, fit_size):
        self.votes    = votes
        self.comments = comments
        self.scores   = scores
        self.time     = 60          # prediction time 
        self.fit_size = fit_size
        self.popt     = []

    def func(self, x, a, d, q):
        e = 0.4
        b = 1
        p = 0.7
        return (a * np.exp( 1-(b / self.time**d )) + q**self.time * e * (x + p*self.comments[:len(x)]) ) /2

    def fit_then_predict(self):
        popt, pcov = curve_fit(self.func, self.votes[:self.fit_size], self.scores[:self.fit_size])
        return popt, pcov


# init
init_votes    = np.array([3,   1,  2,  1,   0])
init_comments = np.array([0,   3,  0,  1,  64])
final_scores  = np.array([26, 12, 13, 14, 229])

# fit and predict
cfit       = Cfit(init_votes, init_comments, final_scores, 15)
popt, pcov = cfit.fit_then_predict()

# plot expectations
fig = plt.figure(figsize = (15,15))
ax1 = fig.add_subplot(2,3,(1,3), projection='3d')
ax1.scatter(init_votes, init_comments, final_scores,                 'go',  label='expected')
ax1.scatter(init_votes, init_comments, cfit.func(init_votes, *popt), 'ro', label = 'predicted')
# axis
ax1.set_xlabel('init votes count')
ax1.set_ylabel('init comments count')
ax1.set_zlabel('final score')
ax1.set_title('fincal score = f(init votes count, init comments count)')

plt.legend()

# evaluation: diff = expected - prediction
diff = abs(final_scores - cfit.func(init_votes, *popt))
ax2  = fig.add_subplot(2,3,4)
ax2.plot(init_votes, diff, 'ro', label='fit: a=%5.3f, d=%5.3f, q=%5.3f' % tuple(popt))
ax2.grid('on')
ax2.set_xlabel('init votes count')
ax2.set_ylabel('|expected-predicted|')
ax2.set_title('|expected-predicted| = f(init votes count)')


# plot expected and predictions as f(init-votes)
ax3  = fig.add_subplot(2,3,5)
ax3.plot(init_votes, final_scores, 'gx', label='fit: a=%5.3f, d=%5.3f, q=%5.3f' % tuple(popt))
ax3.plot(init_votes, cfit.func(init_votes, *popt), 'rx', label='fit: a=%5.3f, d=%5.3f, q=%5.3f' % tuple(popt))
ax3.set_xlabel('init votes count')
ax3.set_ylabel('final score')
ax3.set_title('fincal score = f(init votes count)')
ax3.grid('on')

# plot expected and predictions as f(init-comments)
ax4  = fig.add_subplot(2,3,6)
ax4.plot(init_votes, final_scores, 'gx', label='fit: a=%5.3f, d=%5.3f, q=%5.3f' % tuple(popt))
ax4.plot(init_votes, cfit.func(init_votes, *popt), 'rx', label='fit: a=%5.3f, d=%5.3f, q=%5.3f' % tuple(popt))
ax4.set_xlabel('init comments count')
ax4.set_ylabel('final score')
ax4.set_title('fincal score = f(init comments count)')
ax4.grid('on')
plt.show()

Вывод предыдущего кода следующий: enter image description here Ну, очевидно, предоставленный набор данных слишком мал, чтобы оценить любой подходтак что это зависит от вас, чтобы проверить это больше.

Основная идея здесь заключается в том, что вы предполагаете, что ваши данные соответствуют определенной функции / поведению (описанному в func), но вы даете им определенные степени свободы (ваши параметры: a, d, q) и, используя curve_fit, вы пытаетесь приблизить наилучшую комбинацию этих переменных, которая будет соответствовать вашим входным данным и вашим выходным данным.Как только вы получите возвращенные параметры из curve_fit (в коде popt), вы просто запустите свою функцию, используя эти параметры, например, вот так (добавьте этот раздел в конце предыдущего кода):

# a function similar to func to predict scores for a certain values
def score(votes_count, comments_count, popt):
    e, b, p = 0.4, 1, 0.7
    a, d, q = popt[0], popt[1], popt[2]
    t       = 60
    return (a * np.exp( 1-(b / t**d )) + q**t * e * (votes_count + p*comments_count )) /2

print("score for init-votes = 2 & init-comments = 0 is ", score(2, 0, popt))

Вывод:

score for init-votes = 2 & init-comments = 0 is 14.000150386210994

Вы можете видеть, что этот вывод близок к правильному значению 13, и, надеемся, с большим количеством данных вы сможете получить более точные и точные аппроксимации ваших параметров и, следовательно, лучшие "прогнозы".

...