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:
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.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/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)–(1–true_y)*tf.log(1–predicted_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)) |
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. :)