Antro rinkimų turo prognozė pasitelkiant neuroninius tinklus

Pirmiausia turiu įspėti: nemanau, kad reikėtų į gautus rezultatus žiūrėti labai rimtai. Neuroninio tinklo mokymui naudojau tik 2012-ų metų Seimo rinkimų apygardų duomenis, tad imtis labai nedidelė, o tai turėtų lemti ir gana nemažą paklaidą prognozėse. Galbūt tikslesnių rezultatų būtų galima tikėtis naudojant apylinkių, o ne apygardų duomenis.

Prognozuoti šių metų rezultatus iš 2012-ų metų duomenų nelengva ir dėl stipriai pasikeitusio partijų populiarumo: žalieji valstiečiai prieš ketverius metus nebuvo labai patrauklūs rinkėjams, o ir Skvernelio atsiradimas labai šią partiją pakeitė. Įdomu tai, kad Darbo partijos bei tvarkiečių kritimas iš aukštumų gana gerai atsispindi neuroninio tinklo rezultatuose: jiems prognozuojama laimėti mažiau apygardų nei jie šiuo metu pirmauja.  Kad ir kaip ten būtų, gavau tokį rezultatą:

Prognozė Dabar pirmauja
LVZS 24 21
TSLKD 24 22
LSDP 9 10
LRLS 5 4
LLRA 3 3
TT 2 4
KITI 1 2
DP 1 3
NEP 2 2

Neuroninis tinklas „išmoko“, jog stiprus lenkų pirmavimas apygardoje dažniausiai lemia ir pergalę antrame ture. Algirdui Paleckiui pergalė neprognozuojama, nes istoriniai pernai metų duomenys rodo, jog „Frontui“ ne itin sekėsi – bet jo puikus pasirodymas pirmame ture tikriausiai buvo netikėtas ir daugeliui politikos analitikų. Keisčiausia prognozė, kuria sunku patikėti yra 52-oje Visagino-Zarasų apygardoje, kurioje antrame ture kausis Darbo partija su tvarkiečiais (pergalė prognozuojama Darbo partijai, nors stipriai pirmauja tvarkietis Dumbrava). Keistoka, bet gal ir logiška 40-osios Telšių apygardos prognozė, kur stipriai pirmaujantis darbietis turi mažai šansų atsilaikyti prieš valstietį Martinkų. Kaip jau minėjau, Darbo partijai šis modelis daug šansų nepalieka. Visas apygardų sąrašas su prognozuojamais nugalėtojais ir tikimybėmis, kad nugalės pirmaujantis:

Turint nedaug istorinių duomenų tikriausiai labiau pasitikėčiau politikos ekspertų prognozėmis konkrečioje apygardoje arba modeliuočiau tikimybes kiek kurios partijos rėmėjų ateis į antrą turą bei palaikys ne savo partijos kandidatą: būtent tokį modelį ruošia WebRobots komanda, kuri leido man pasinaudoti jų surinktais iš VRK duomenimis. Idėja patreniruoti neuroninį tinklą ir kilo susidūrus su problema ar nebūtų galima kaip nors statistiškai išskaičiuoti tikimybių, kiek, tarkim, socialdemokratų palaikytų konservatorių kandidatą jei jis būtų likęs prieš darbietį. Taip pat galima pažiūrėti į Vaidoto Zemlio prognozes.

Post Mortem

Rezutatai buvo stipriai kitokie, nei buvo tikimasi: daugiausiai prašauta (tikriausiai dėl to, kad 2012-aisias valstiečiai pasirodė ne itin įspūdingai) su LVŽS ir TSLKD. Tam tikros tendencijos buvo teisingos – Darbo partija, Tvarka ir Teisingumas bei Socialdemokratai iš tiesų gavo mažiau mandatų nei buvo pirmaujama po pirmo turo, tuo tarpu liberalai sugebėjo laimėti daugiau apygardų nei pirmavo po pirmo turo, tačiau šių pokyčių mastas buvo žymiai (žymiai žymiai) didesnis. Iš viso, neuroniniai tinklai sugebėjo atspėti 48 apygardas (67% tikslumas). Palyginimui – rankomis dėliotas Webrobots komandos modelis pasiekė 80% tikslumą. Tiesa, atmetus kai kuriuos nelogiškus neuroninio tinklo siūlymus, kurie plika akimi atrodė keisti ir pataisius prognozę Dainavos apygardoje dėl Vinkaus skandalo (ko iš 2012-ųjų duomenų niekaip nebuvo galima žinoti), buvo galima pasiekti maždaug 75% procentų tikslumą. Ne kažką, bet šis tas.

Skaičiuojant modelio patikimumą, dažnai žiūrimas plotas po Receiver Operating Characteristic (ROC) kreive (kuo gerenis modelis, tuo jis turėtų artėti link vieneto). Štai modelių palyginimai:

Area under ROC curve
Neuroninis tinklas (tikimybės) 0.597143
Webrobots modelis 0.708095
Neuroninis tinklas (binarinis) 0.549048
Laimės pirmaujantis 1 ture 0.500000
Laimės pirmaujantis daugiamandatėje 0.487619

O čia pačios ROC kreivės:

Skirtingų modelių ROC kreivės

 

Techniniai dalykai

Didžioji dalis tolimesnės informacijos bus įdomi tik visokiems programuotojams, nes tai tik techninės detalės.

Neuroninis tinklas su dviem paslėptais sluoksniais (iš 20 ir 10 neuronų) mokomas iš požymių, kurie yra 2012-ųjų metų vienmandačių apygardų pirmo turo rezultatai pagal partiją. y vektoriuje žymima 1 jeigu pirmaujantis pirmame ture laimėjo ir antrąjį bei žymima 0, jeigu nugalėtojas tarp turų pasikeitė. Kaštų funkcijoje pridedamas nedidelis reguliarizacijos parametras, nes be jo tinklas „persimoko“ (overfitting). Vėliau išmokytam tinklui duodami 2016-ų metų pirmo turo rezultatai (tiesa, šiek tiek pakeičiama partijų pavadinimai, kad jie kuo arčiau atitiktų 2012-ųjų metų partijų bei koalicijų pavadinimus) ir iš čia gaunama prognozė. Visa tai daryta su Python ir Tensorflow.

#!/Users/petras/.conda/envs/tensorflow3/bin/python
import tensorflow as tf
import numpy as np
import pandas as pd
import datetime
import time
#####
ITERATIONS = 100
TRAINING_STEPS = 2000
OPTIM = 0.01
REG_PARAM = 5e-6
INCLUDE_DAUG = False
INCLUDE_AKT = False
NORMALIZE = False
LOG = False
df = pd.read_csv('2012_I.csv')
df.loc[df.duplicated(['apygarda', 'partija']), 'partija'] = 'Išsikėlė pats 2'
df.loc[df.duplicated(['apygarda', 'partija']), 'partija'] = 'Išsikėlė pats 3'
df = df[['apygarda', 'partija', 'pct_dalyvavusiu']]
df = df[df.apygarda != '43. Kėdainių']
df = df[df.apygarda != '56. Vilniaus – Šalčininkų']
df = df[df.apygarda != '68. Vilkaviškio']
df = df[df.apygarda != '52. Zarasų – Visagino'] # hmm…
df.pct_dalyvavusiu = df.pct_dalyvavusiu.str.replace(',','.')
df.pct_dalyvavusiu = df.pct_dalyvavusiu.str.rstrip('%')
df.pct_dalyvavusiu = df.pct_dalyvavusiu.astype(float)
df.pct_dalyvavusiu = df.pct_dalyvavusiu / 100
df = df.pivot(index='apygarda', columns='partija', values='pct_dalyvavusiu').fillna(0)
df['Tėvynės sąjunga – Lietuvos krikščionys demokratai'] = df['Tėvynės sąjunga – Lietuvos krikščionys demokratai, išsikėlė pats'] + \
df['Tėvynės sąjunga – Lietuvos krikščionys demokratai']
df['Politinė partija „Lietuvos sąrašas“'] = df['Politinė partija „Lietuvos sąrašas“, išsikėlė pats'] + \
df['Politinė partija „Lietuvos sąrašas“']
df.drop('Tėvynės sąjunga – Lietuvos krikščionys demokratai, išsikėlė pats', axis=1)
df.drop('Politinė partija „Lietuvos sąrašas“, išsikėlė pats', axis=1)
df2 = pd.read_csv('2012_II.csv')
df2 = df2[['apygarda', 'partija', 'viso']]
df2 = df2.pivot(index='apygarda', columns='partija', values='viso').fillna(0)
df2['Tėvynės sąjunga – Lietuvos krikščionys demokratai'] = df2['Tėvynės sąjunga – Lietuvos krikščionys demokratai, išsikėlė pats'] + \
df2['Tėvynės sąjunga – Lietuvos krikščionys demokratai']
#df = (df – df.mean()) / (df.max()- df.min())
df['isliko'] = (df.idxmax(axis=1) == df2.idxmax(axis=1)).astype(float)
if(INCLUDE_DAUG):
df_daug = pd.read_csv('2012_daugiam.csv')
df_daug = df_daug[['apygarda', 'vardas', 'pct_dalyvavusiu']]
df_daug = df_daug[df_daug.apygarda != '43. Kėdainių']
df_daug = df_daug[df_daug.apygarda != '56. Vilniaus – Šalčininkų']
df_daug = df_daug[df_daug.apygarda != '68. Vilkaviškio']
df_daug = df_daug[df_daug.apygarda != '52. Zarasų – Visagino'] # hmm…
df_daug.pct_dalyvavusiu = df_daug.pct_dalyvavusiu.str.replace(',','.')
df_daug.pct_dalyvavusiu = df_daug.pct_dalyvavusiu.str.rstrip('%')
df_daug.pct_dalyvavusiu = df_daug.pct_dalyvavusiu.astype(float)
df_daug.pct_dalyvavusiu = df_daug.pct_dalyvavusiu / 100
df_daug = df_daug.pivot(index='apygarda', columns='vardas', values='pct_dalyvavusiu').fillna(0)
df_daug.columns = df_daug.columns + ' daugiamandate'
df = pd.concat([df, df_daug], axis=1, join="inner")
if(INCLUDE_AKT):
df_akt = pd.read_csv('2012_aktyvumas.csv', index_col=0)
df_akt.aktyvumas = df_akt.aktyvumas.astype(float) / 100
df_akt = df_akt[df_akt.index != '43. Kėdainių']
df_akt = df_akt[df_akt.index != '56. Vilniaus – Šalčininkų']
df_akt = df_akt[df_akt.index != '68. Vilkaviškio']
df_akt = df_akt[df_akt.index != '52. Zarasų – Visagino'] # hmm…
if(NORMALIZE):
df_akt = (df_akt df_akt.mean()) / (df_akt.max() df_akt.min())
df = pd.concat([df, df_akt], axis=1)
df3 = pd.read_csv('2016_I.csv')
df3.loc[pd.isnull(df3.partija), 'partija'] = 'Išsikėlė pats'
df3.loc[df3.duplicated(['apygarda', 'partija']), 'partija'] = 'Išsikėlė pats 2'
df3.loc[df3.duplicated(['apygarda', 'partija']), 'partija'] = 'Išsikėlė pats 3'
df3.loc[df3.partija=='Lietuvos laisvės sąjunga (liberalai)', 'partija'] = 'Politinė partija „Sąjunga Taip“'
df3.loc[df3.partija=='Lietuvos lenkų rinkimų akcija-Krikščioniškų šeimų sąjunga', 'partija'] = 'Lietuvos lenkų rinkimų akcija'
df3.loc[df3.partija=='Lietuvos žaliųjų partija', 'partija'] = 'Politinė partija „Lietuvos žaliųjų sąjūdis“'
df3.loc[df3.partija=='Antikorupcinė N. Puteikio ir K. Krivicko koalicija (Lietuvos centro partija, Lietuvos pensininkų partiją)', 'partija'] = 'Lietuvos centro partija'
df3.loc[df3.partija=='Lietuvos liaudies partija', 'partija'] = 'Respublikonų partija'
df3.loc[df3.partija=='S. Buškevičiaus ir Tautininkų koalicija „Prieš korupciją ir skurdą“ (Partija „Jaunoji Lietuva“, Tautininkų sąjunga)', 'partija'] = 'Partija „Jaunoji Lietuva“'
df3.loc[df3.vardas=='ALGIRDAS PALECKIS', 'partija'] = 'Socialistinis liaudies Frontas'
df3.pct_dalyvavusiu = df3.pct_dalyvavusiu.str.replace(',','.')
df3.pct_dalyvavusiu = df3.pct_dalyvavusiu.str.rstrip('%')
df3.pct_dalyvavusiu = df3.pct_dalyvavusiu.astype(float)
df3.pct_dalyvavusiu = df3.pct_dalyvavusiu / 100
df3 = df3.pivot(index='apygarda', columns='partija', values='pct_dalyvavusiu').fillna(0.0)
if(INCLUDE_DAUG):
df3_daug = pd.read_csv('2016_daugiam.csv')
df3_daug.loc[df3_daug.vardas=='Lietuvos laisvės sąjunga (liberalai)', 'vardas'] = 'Politinė partija „Sąjunga Taip“'
df3_daug.loc[df3_daug.vardas=='Lietuvos lenkų rinkimų akcija-Krikščioniškų šeimų sąjunga', 'vardas'] = 'Lietuvos lenkų rinkimų akcija'
df3_daug.loc[df3_daug.vardas=='Lietuvos žaliųjų partija', 'vardas'] = 'Politinė partija „Lietuvos žaliųjų sąjūdis“'
df3_daug.loc[df3_daug.vardas=='Antikorupcinė N. Puteikio ir K. Krivicko koalicija (Lietuvos centro partija, Lietuvos pensininkų partiją)', 'vardas'] = 'Nacionalinis susivienijimas „Už Lietuvą Lietuvoje“ (Lietuvos centro partijos, Lietuvos socialdemokratų sąjungos, Tautininkų sąjungos ir Tautos vienybės sąjungos koalicija)'
df3_daug.loc[df3_daug.vardas=='Lietuvos liaudies partija', 'vardas'] = 'Respublikonų partija'
df3_daug.loc[df3_daug.vardas=='S. Buškevičiaus ir Tautininkų koalicija „Prieš korupciją ir skurdą“ (Partija „Jaunoji Lietuva“, Tautininkų sąjunga)', 'vardas'] = 'Partija „Jaunoji Lietuva“'
df3_daug = df3_daug[['apygarda', 'vardas', 'pct_dalyvavusiu']]
df3_daug.pct_dalyvavusiu = df3_daug.pct_dalyvavusiu.str.replace(',','.')
df3_daug.pct_dalyvavusiu = df3_daug.pct_dalyvavusiu.str.rstrip('%')
df3_daug.pct_dalyvavusiu = df3_daug.pct_dalyvavusiu.astype(float)
df3_daug.pct_dalyvavusiu = df3_daug.pct_dalyvavusiu / 100
df3_daug = df3_daug.pivot(index='apygarda', columns='vardas', values='pct_dalyvavusiu').fillna(0)
df3_daug = df3_daug.drop('Politinė partija „Lietuvos sąrašas“', axis=1)
df3_daug = df3_daug.drop('Politinė partija „Lietuvos žaliųjų sąjūdis“', axis=1)
df3_daug.columns = df3_daug.columns + ' daugiamandate'
df3 = pd.concat([df3, df3_daug], axis=1, join="inner")
if(INCLUDE_AKT):
df3_akt = pd.read_csv('2016_aktyvumas.csv', index_col=0)
df3_akt.aktyvumas = df3_akt.aktyvumas.astype(float) / 100
if(NORMALIZE):
df3_akt = (df3_akt df3_akt.mean()) / (df3_akt.max() df3_akt.min())
df3 = pd.concat([df3, df3_akt], axis=1)
df3 = pd.DataFrame(columns=df.columns).append(df3, ignore_index=True).fillna(0)
#df3 = (df3 – df3.mean()) / (df3.max()- df3.min())
df3 = np.asarray(df3.drop('isliko', axis=1).astype(float))
results = np.zeros(shape=(ITERATIONS, 71))
####################
### Tensorflow setup
####################
x = tf.placeholder(tf.float32, [None, df3.shape[1]], name='x')
W = tf.Variable(tf.random_normal([df3.shape[1], 20], stddev=0.3), name='W')
b = tf.Variable(tf.random_normal([20], stddev=0.5), name='b')
true_y = tf.placeholder(tf.float32, [None, 1], name='true_y')
y1 = tf.nn.sigmoid(tf.matmul(x, W) + b)
W1 = tf.Variable(tf.random_normal([20, 10], stddev=0.3), name="W1")
b1 = tf.Variable(tf.random_normal([10], stddev=0.5), name='b1')
y2 = tf.nn.sigmoid(tf.matmul(y1, W1) + b1)
W2 = tf.Variable(tf.random_normal([10, 1], stddev=0.3), name="W2")
b2 = tf.Variable(tf.random_normal([1], stddev=0.5), name="b2")
predicted_y = tf.nn.sigmoid(tf.matmul(y2, W2) + b2)
cost = tf.reduce_mean(true_y*tf.log(predicted_y+0.0001)(1true_y)*tf.log(1predicted_y+0.0001)) \
+ tf.reduce_sum(tf.abs(W))*REG_PARAM \
+ tf.reduce_sum(tf.abs(b))*REG_PARAM \
+ tf.reduce_sum(tf.abs(W1))*REG_PARAM \
+ tf.reduce_sum(tf.abs(b1))*REG_PARAM \
+ tf.reduce_sum(tf.abs(W2))*REG_PARAM \
+ tf.reduce_sum(tf.abs(b2))*REG_PARAM
train_step = tf.train.AdamOptimizer(OPTIM).minimize(cost)
if(LOG):
test_cost = tf.scalar_summary("Test cost", cost)
correct_prediction = tf.equal(tf.greater(predicted_y, 0.5),tf.cast(true_y, tf.bool), name="prediction_check")
false_positives = tf.logical_and(tf.greater(predicted_y, 0.5), tf.logical_not(tf.cast(true_y, tf.bool)), name="false_pos")
fp = tf.reduce_sum(tf.cast(false_positives, tf.float32))
false_negatives = tf.logical_and(tf.less_equal(predicted_y, 0.5), tf.cast(true_y, tf.bool), name="false_neg")
fn = tf.reduce_sum(tf.cast(false_negatives, tf.float32))
true_positives = tf.logical_and(tf.greater(predicted_y, 0.5), tf.cast(true_y, tf.bool), name="true_pos")
tp = tf.reduce_sum(tf.cast(true_positives, tf.float32))
accuracy = tf.reduce_sum(tf.cast(correct_prediction, tf.float32))/(tf.reduce_sum(tf.cast(correct_prediction, tf.float32))+fp+fn)
ac_summary = tf.scalar_summary('Accuracy', accuracy)
precision = tp / (fp + tp)
tf.scalar_summary("Precision", precision)
recall = tp / (tp + fn)
tf.scalar_summary("Recall", recall)
F1_score = 2*precision*recall / (precision + recall)
F1_score_summary = tf.scalar_summary("F1_score", F1_score)
tf.scalar_summary("False positives", fp)
tf.scalar_summary("False negatives", fn)
init = tf.initialize_all_variables()
if(LOG):
merged = tf.merge_all_summaries()
# Do the runs and then average the probabilities
################################################
for iteration in range(ITERATIONS):
print("Iteration: %i"%iteration)
train=df.sample(frac=0.8)
test=df.drop(train.index)
train_y = np.asarray(train.isliko)
test_y = np.asarray(test.isliko)
train = np.asarray(train.drop('isliko', axis=1).astype(float))
test = np.asarray(test.drop('isliko', axis=1).astype(float))
train_y = np.reshape(train_y, (train_y.shape[0], 1))
test_y = np.reshape(test_y, (test_y.shape[0], 1))
s = tf.Session()
s.run(init)
if(LOG):
summary_writer = tf.train.SummaryWriter('/tmp/basic/'+datetime.datetime.fromtimestamp(time.time()).strftime('%Y-%m-%d_%H:%M:%S')+'_'+str(TRAINING_STEPS)+'_'+str(OPTIM),
graph = tf.get_default_graph())
for i in range(TRAINING_STEPS):
if(LOG):
_, c, summary = s.run([train_step, cost, merged], feed_dict={x: train, true_y: train_y})
summary_writer.add_summary(summary, i)
if i % 1000 == 0:
s.run([ac_summary, F1_score_summary, test_cost], feed_dict={x:test, true_y:test_y})
else:
_, c = s.run([train_step, cost], feed_dict={x: train, true_y: train_y})
### Now we have the Neural Network trained, so let's plug the 2016 data and see the outcome
results[iteration] = s.run([predicted_y], feed_dict={ x: df3 })[0].T
print(results.mean(axis=0))
print((results > 0.5).astype(int).sum(axis=0))

view raw
tfmodel.py
hosted with ❤ by GitHub

One Comment

  1. Yey~ TF! Kažkas įdomaus :) Kaip sakant, pasilieku peržiūrėjimui vėliau. Šiuo metu yra dalykų, kuriuos reikia greičiau spręsti, kaip kad verslo planų, kurių sėkmė galėtų sudaryti sąlygas masiškai įdarbinti žmones per internetą naudojantis kibernetinėmis pirštinėmis, ir miniatiūrizuotais čiuptuvais, įgalinsiančiais sukurti darbo jėgą reikalingą pagydyti didelius žmonių skaičius, kūrimas ir vertinimas. :)

Comments are closed.