Jugons quelques livres par leurs couvertures

47

Tout le monde sait que le contenu rend la question. Mais un bon titre aide aussi, et c'est la première chose que nous voyons. Il est temps de transformer cette première impression en un programme et de déterminer quels types de titres obtiennent plus de votes positifs.

Vous êtes par conséquent invité à écrire un programme ou une fonction prenant le titre d’une question PPCG en tant qu’entrée et renvoyant une prédiction de son score.

Par exemple, vous pouvez recevoir Counting Grains of Riceune entrée et essayer de renvoyer quelque chose de proche du score, 59dans ce cas. Les suppositions non entières vont bien, mais les suppositions inférieures ou égales -20ne le sont pas.

Voici les données, pour les tests et les scores:

http://data.stackexchange.com/codegolf/query/244871/names-and-upvotes

Notation: Votre programme sera exécuté sur toutes les questions de l'historique de ce site (PPCG), sans compter les questions fermées. La fonction ln(score + 20)sera ensuite appliquée à chaque score et à chaque conjecture. L'erreur racine moyenne moyenne entre les deux ensembles de valeurs résultants est votre score. Plus c'est bas, mieux c'est.

Par exemple, un programme qui devine 0 à chaque fois obtiendrait un score de 0,577, tandis qu'un programme qui en devinerait 11 à chaque fois obtiendrait un score de 0,362.

Veuillez calculer votre score et l'inclure dans le titre de votre réponse. Veuillez également inclure la prévision de votre programme pour le nombre de votes positifs que cette question obtiendra.

Restrictions

  • Pour éviter un codage en dur excessif, ne pas dépasser 1000 caractères.

  • Doit fonctionner sur l’ensemble des données ci-dessus en moins d’une minute sur une machine raisonnable.

  • Les échappatoires standard sont fermées.


Voici un testeur écrit en Python, pour votre usage et / ou pour lever les ambiguïtés:

import sys
import math
import csv

scores_dict = {}

with open(sys.argv[1], 'r') as csv_file:
    score_reader = csv.reader(csv_file)
    for score, title in score_reader:
        if score == 'Score':
            continue
        scores_dict[title] = int(score)

def rate_guesses(guesser):
    def transform(score):
        return math.log(score + 20) if score > -20 else 0
    off_by_total = 0
    lines_count = 0
    for title in scores_dict:
        guessed_score = guesser(title)
        real_score = scores_dict[title]
        off_by_total += (transform(real_score) - transform(guessed_score)) ** 2
    return (off_by_total/len(scores_dict)) ** .5

def constant11(title):
    return 11

print(rate_guesses(constant11))
isaacg
la source
19
C'est une bonne idée, mais dommage que le jeu de données ne soit pas stable, les scores deviendront invalides par la suite. Il existe également une possibilité mineure de vote stratégique: quiconque répond à cette question et gagne un badge vox-populi au cours de la même semaine doit être considéré avec suspicion! ;-)
Level River St
1
Le titre inclura-t-il ou exclura-t-il des choses comme [closed]et [on hold], le cas échéant?
es1024
4
@steveverrill Eh bien, le revers de la médaille, c’est qu'avec le temps, nous pourrons voir si les programmes fonctionnent aussi bien pour les publications futures que pour les publications antérieures.
isaacg
6
Il est difficile de vaincre le codage en dur. Chaque question codée en dur avec le plus grand nombre de votes peut réduire jusqu'à 0,4 point. Et il semble y avoir pas beaucoup de modèle commun aussi, haha. Je prédis que les réponses se disputeront simplement sur la manière d'adapter autant de résultats codés en dur sur 1 000 octets.
moitié
5
Vous ne devez pas utiliser l'ensemble des questions comme test. Vous devez présélectionner au hasard un certain nombre (10% à 20%) et les définir comme votre ensemble de tests (mais ne rien dire à qui que ce soit). Il est beaucoup plus facile de créer un algorithme qui prédit l’historique, qu’un algorithme ayant une valeur prédictive future (c’est-à-dire qui fonctionne bien sur un sous-ensemble donné). (Il serait même préférable de supprimer ces 10% de ce que nous pouvons voir, mais cela ne fonctionnerait pas vraiment.)
Joe

Réponses:

9

Python 2, 991 caractères, score 0,221854834221, prédire 11

import base64
e={}
d=base64.decodestring('vgAcRAEVDAIsqgQYalYaggcjQKwVXAoZWAsYQg0Ckg4VlWEX9hEDRhMe0hckCBkeuhsW3CAWQiEm\nSiMZMiwgTDAZZjIcSLMZfDQDnjwCe2AVaEQCYWEBIEgnDEoXzk0e3lQb5FYVKlkVZlwB+F0XwmI/\nGmRcuGUXWmYdVGkbzmwafG8eaHMdInkggHonRn5sKoMXgIkpbowVOI4cNJAubpQdYpcydJgVypkA\nZpweMp8ZsqEcRKMghKQYkKVPPXEWMqkWHKwbjrEdzLIBNLMf1LQivrYC99UV9rxNRsABNMEiPzob\npc0ActAhn3gcrNUZYNcWYNov/t8VgOEXAuMYqOUWsqUiCPIefPWNbvtKevwWvP0Cl9UsjQMdWwQb\nfQdpJQgWYwkCZRLBjxMWWdkqHRkWNxwB6x8p2SEZyyICSyYcgysaOS0CUy8hezAaGeEVpTRQ3zUz\nZzkZRzohizwwST4c8UAdF0OqG9AXIuEYYRN6208nU1AktVEVJ1IVWeMkIVQXdY4D2VYYD/cYb1om\nG1xA0zoY3uUaRWAhWpBSHWUXQTxGe+cad20CO3AZX3EBw3IiMXcef3gecXsVGXwhw30VbX4W24BD\n6qyQ45YaYYgZ4YobbYwauY4bMY82HZEdO5YmQ5cV35sVxaMbY6gYNas576ws+bADO7QpN7hdLJ8B\n4Eoks8EYX8VU68cYWfcar82QOdAaxdEfQ9UiW/kXL9k2ddwCW90m694enqUCkeEBE+IYWvsfA1FC\nJ+spMVIjhe4WEP0fAfYax/c3MfgbgfkqP/0DLf4V\n')
for i in range(0,600,3):
 e[ord(d[i])*256+ord(d[i+1])]=ord(d[i+2])*2-8
def p(t):
 return e.get(hash(t)%256**2,11)

Explication:

C’est un codage dur éhonté, mais nous essayons de le faire efficacement.

Prétraitement:

Dans un code séparé, j'ai haché chaque titre à une valeur comprise entre 0 et 256 ^ 2-1. Appelons ces bacs de valeurs. Pour chaque bin, j'ai calculé le score moyen. (La moyenne est nécessaire car pour une infime fraction des bacs, il y a des collisions - plus d'un hachage de titres dans le même bac. Mais pour la grande majorité, chaque titre est mappé sur un bac lui-même).

L'idée derrière le code à 2 octets pour chaque titre est qu'un 1 octet n'est pas suffisant - nous rencontrons trop de collisions, nous ne savons donc pas vraiment quel score attribuer à chaque groupe d'un octet. Mais avec les bacs de 2 octets, il n'y a presque pas de collisions et nous obtenons effectivement une représentation de 2 octets de chaque titre.

Ensuite, classez les bacs - calculez le gain en score si nous attribuons à ce bac sa valeur calculée, au lieu de simplement deviner 11. Prenez les N premiers bacs et codez-les dans une chaîne (qui est d dans le code réel).

Le codage: la clé de la corbeille est codée sur 2 octets. la valeur est codée en utilisant 1 octet. J'ai trouvé des valeurs comprises entre -8 et 300 + quelque chose, donc j'ai dû serrer un peu pour le faire en 1 octet: x -> (x + 8) / 2.

Code actuel:

lu d comme des triplets d’octets, décodant le codage expliqué ci-dessus. Lorsqu'un titre est donné, calculez son hachage (modulo 256 ^ 2), et si cette clé est trouvée dans le dict, renvoyez la valeur à laquelle il correspond. Sinon, retournez 11.

Ofri Raviv
la source
3
Une suggestion: le score moyen n'est pas si bon. Regardez la fonction de notation des défis, elle n’est pas linéaire.
Déduplicateur
1
@DuDuplicator Merci, je me suis rendu compte qu'après avoir terminé. Le fait est que, pour 99% des casiers, il n’ya pas de collision, donc la moyenne est en fait juste le score du titre unique qui correspond à ce casier.
Ofri Raviv
16

Javascript ES6

Score: 0.245663
Longueur: 1000 octets
Prédits: 5

(Je suppose que la question est due à une avalanche inattendue de votes négatifs.: P)

Minified

E="ABCDEFGHIJKLMNOPQRSTUVWXYZ";E=E+E.toLowerCase()+"0123456789!@#$%;*()";P="RCRHIFMGPGIDQKHMJLLRMLFJGKHEqHPOgJNKGPCHPJNUPOSGJQKKKMELMIJHLKIKNcKDOfSJLFHDILGKIOUKLMLLKMKIHGHJGIIJDGJKIHIIFIGMTIHFJMIKDJGQJKGMKJHPRJPLMGIOPIIIIPBYFMGLEIKIMMRUKFLFGJFKFTHPFODEQTGTHKIJDHNJGDGiHKNYLHHDLJHGILOEViKNEKGQZfHJMIISIJFRHKLJMLPIFJILKKKJKKJESHNLLIKIGKGJJJHKJRGULLSLRONJNEeLKIQGGPQIHPLEIHHIDXjQHNBKOGWWIENRbYoHINHNMKTNKHTGMIPXFJLLMLIHPPLDJJKFUMIQMOKJLJJKIJKNLHRILJIAIbJEZOGIELGHGLOINDPJMJeJWRIIJHSOZDOsOFRRIOIOTIJSGGJKFUIDOINGOcLQEJFEITLMNIIGIGIMG7LPSNLKVOKIFGHJITGOFUJIIRN";K={};"99r1501vz076mip077myv0733it280exx081gt9118i1g279dyx102uho203ejd07433z087uje097kdg1567ft2088rk275dmu1203ez106lih1763ei126f6q101aax084owh088aid161i9y179gvn236ptn3338vf132i55080fke101l4z3789ai281ulm081blm124euz074o5m07513z14117l095qdn092gl30757n5153".replace(/(...)(...)/g,(_,a,b)=>K[a]=1*b);D=40655;N=479;H=(s,T)=>(h=7,[...s].map(c=>h=~~(h*T+c.charCodeAt(0))),h);S=s=>(y=H(s,31),K[((y%D+D)%D).toString(36)]||E.indexOf(P[(H(s,48)%N+N)%N]));

Étendu

E = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
E = E + E.toLowerCase() + "0123456789!@#$%;*()";
K = {};
P = "RCRHIFMGPGIDQKHMJLLRMLFJGKHEqHPOgJNKGPCHPJNUPOSGJQKKKMELMIJHLKIKNcKDOfSJL" +
    "FHDILGKIOUKLMLLKMKIHGHJGIIJDGJKIHIIFIGMTIHFJMIKDJGQJKGMKJHPRJPLMGIOPIIIIP" +
    "BYFMGLEIKIMMRUKFLFGJFKFTHPFODEQTGTHKIJDHNJGDGiHKNYLHHDLJHGILOEViKNEKGQZfH" +
    "JMIISIJFRHKLJMLPIFJILKKKJKKJESHNLLIKIGKGJJJHKJRGULLSLRONJNEeLKIQGGPQIHPLE" +
    "IHHIDXjQHNBKOGWWIENRbYoHINHNMKTNKHTGMIPXFJLLMLIHPPLDJJKFUMIQMOKJLJJKIJKNL" +
    "HRILJIAIbJEZOGIELGHGLOINDPJMJeJWRIIJHSOZDOsOFRRIOIOTIJSGGJKFUIDOINGOcLQEJ" +
    "FEITLMNIIGIGIMG7LPSNLKVOKIFGHJITGOFUJIIRN";

   ("99r1501vz076mip077myv0733it280exx081gt9118i1g279dyx102uho203ejd07433z087u" +
    "je097kdg1567ft2088rk275dmu1203ez106lih1763ei126f6q101aax084owh088aid161i9" +
    "y179gvn236ptn3338vf132i55080fke101l4z3789ai281ulm081blm124euz074o5m07513z" +
    "14117l095qdn092gl30757n5153").
        replace( /(...)(...)/g, (_,a,b) => K[a] = 1*b );

D = 40655;
N = 479;
H = (s,T) => (
    h = 7,
    [...s].map( c => h = ~~(h*T + c.charCodeAt(0)) ),
    h
);

S = s => (
    y = H( s, 31 ),
    K[((y%D + D)%D).toString( 36 )] || E.indexOf( P[(H( s, 48 )%N + N)%N] )
);

La fonction Saccepte une chaîne (titre) et renvoie son score.

Notes sur le comportement:

  • ≤ 70 titres de vote sont traités séparément de> 70 titres de vote
  • ≤ 70 titres de votes sont triés dans des bacs en utilisant un algorithme d'optimisation potentielle hautement sophistiqué, basé sur le suivi de mots clés, qui ne ressemble en aucun cas à une fonction de hachage de chaîne.
  • après un peu de calcul heureux, il s'avère que la meilleure estimation pour chaque casier est simplement la moyenne géométrique des (votes + 20) pour tous les titres dans le casier, moins 20
  • les suppositions optimales pour les 479 cases sont ensuite codées en chaîne de base 70 sur 479 caractères
  • pour> 70 titres de vote, les titres se voient attribuer des codes uniques à la base 36 à 3 chiffres générés à l'aide d'une technique de hachage de pointe garantissant l'absence de collisions avec d'autres titres> 70 votes et aucune fausse détection de ≤ 70 titres. Cette technique de pointe ne ressemble en aucun cas à l’essai de comptages aléatoires jusqu’à ce que l’on ne produise aucune collision.
  • les codes de titre de vote> 70 et leur nombre de votes sont codés dans une chaîne (6 octets par titre), qui est convertie en une simple table de correspondance. La routine n'a donc aucune erreur pour tous les titres> 70 votes.
COTO
la source
10

Python 2, score = 0,335027, 999 caractères, prédisez 11,34828 pour cette question

Juste pour faire avancer les choses. Ceci n'est nulle part optimal.

SVM n’est qu’une idée aléatoire et j’ai eu l’impression de la mettre en œuvre, alors la voici. Cela améliore la ligne de base de 0,02 point, je suis donc assez content de cela. Mais pour montrer que le codage en dur est à l'origine de la majorité des améliorations, je codifie également certaines réponses.

Sans le codage en dur, le score est de 0,360 (et toutes les prédictions se situent autour de 11, haha)

J'utilise scikit-learn et nltk

import sys
import math
import csv
from sklearn.feature_extraction.text import TfidfVectorizer as TV
from sklearn.svm import SVR
from nltk.stem.porter import PorterStemmer as PS
sd = {}
lr = None
tv = None
with open(sys.argv[1], 'r') as cf:
    sr = csv.reader(cf)
    for sc, t in sr:
        if sc == 'Score':
            continue
        sd[t] = int(sc)
ts,scs = zip(*sd.items())
s = PS()
def analyzer(st):
    r = []
    for word in st.split():
        if word.isdigit():
            r.append('<NUM>')
        elif not word.isalnum():
            r.append('<PUNCT>')
        else:
            r.append(s.stem(word.lower()))
    return r
tv = TV(min_df=25, stop_words='english', analyzer=analyzer)
ti = tv.fit_transform(ts)
lr = SVR()
lr.fit(ti, scs)
d={'4 w':378,'y 42':333,'eeta':280,'an Got':279,'s 2 ':275,"e I'":208,'r CP':203,'? No':179,'B.O':156}
def c(t):
    for s in d.keys():
        if s in t:
            return d[s]
    t = tv.transform([t])
    r = lr.predict(t)[0]+1.5
    return r
à moitié
la source
Je ne suis pas sûr de comprendre. Vous avez lu les partitions à partir d'un fichier externe? Alors pourquoi ne pas simplement prédire sd [t]? Cela donnerait un score de 0 ...
Ofri Raviv
2
Parce que ce ne serait pas amusant = p
demi-
4

Python 2, 986 caractères, score de 0.3480188, prédire 12

M,S=14.29,23.02
D=lambda x:[ord(y)-32 for y in x]
w='fiLoNoNuMiNoTiMoShOnLoGoLeThRantexgensuBaSqUnInPeReGaMuLinprOuThInThEvEnClOpExPyThADdiSoLiSiMaTrEqUsImAsCoManstARrePoWoReawhapoLandradeTyPaLOsoReCreprediVeReSpebeTiPrImPladaTrihelmakwhicolpaReValpaTrafoROsoumovfinfunpuzyoufaSeQuiwhecoDeChagivcouchehanpoStrdiGraconjavwricalfrowitbinbrafrabuipoi'
for i in range(26):w=w.replace(chr(65+i),chr(97+i)*2)
w=[w[i:i+3]for i in range(0,372,3)]
m=D("(+),+)+=*...+..++'(*)5)/2)*43++16+0,+33*4*/(0.'+>-)+13+(2?8+6;,3;43+4(+.('(,.*.*+56+6)0((++(B,))+E0,-7/)/*++),+***)2+(3(.*)'")
s=D("))'B)'*j+:51(*3+0')+)^'/<-+MG,-1=),-0:A+T&J&K1%,O*()4Y-%:_A.=A*C]MJ-N%)5(%%-0+).*3Q(M&0'%(+$p*)()a8:-T?%5(-*'/.'+)+@,'J&1'&&")
def G(x,m,s):s=s or 1e-9;return(.4/s)*(2.78**(-(x-m)**2./(2*s*s)))
d={w[i]:(m[i],s[i])for i in range(124)}
def Z(t,c):
 p=1
 for W in t.split():
  if len(W)>3:
   W=W[:3].lower()
   if W in d:p*=G(c,*d[W])
 return p*G(c,M,S)
def J(t):return max([(Z(t,r),r)for r in range(-9,99)])[1]

La fonction pertinente est J.

Le programme est essentiellement Naive Bayes utilisant des mots de titre comme fonctionnalités, mais il est extrêmement limité grâce à la limite de caractères. Comment restreint? Bien...

  • Pour chaque titre, nous convertissons en minuscules et ne regardons que les mots d'au moins 4 lettres. Ensuite, nous prenons les trois premières lettres de chacun de ces mots comme caractéristiques. Nous omettons de supprimer la ponctuation pour économiser sur les caractères.
  • Nous choisissons uniquement les triplets de lettres qui sont au début de 19 mots au moins (ceux-ci sont stockés wci-dessus). La compression est effectuée en réarrangeant les triplets de manière à ce qu'il y ait autant de lettres doubles que possible, et que ces paires soient remplacées par leur majuscule ASCII correspondante (par exemple, fiLoNoN ... → fil, lon, non, ...)
  • Pour chaque triplet, nous examinons les scores des titres dans lesquels il apparaît et calculons la moyenne et l'écart type des scores. Ensuite, nous convertissons ceux-ci en entiers et les stockons dans m, sci - dessus, en utilisant le fait que la moyenne / sd est au maximum de 90 (permettant un codage ASCII direct, car il existe 95 ASCII imprimables)
  • G est la fonction de distribution normale - nous arrondissons e à 2dp et la racine carrée inverse de 2 pi à 1 dp pour économiser sur les caractères.

Globalement, la limite extrême de carbonisation en faisait l’une des pires idées que j’ai jamais proposée, mais je suis assez contente du travail que j’ai réussi à faire (même si cela ne fonctionne pas très bien). Si quelqu'un a de meilleures idées pour la compression, merci de me le faire savoir :)

(Merci à KennyTM d'avoir signalé ma compression inutile)

Sp3000
la source
Sauf si j'ai mal exécuté le code, votre code de compression est encore plus long que le résultat décompressé ... w='grge…scse';w=[w[i:i+2]for i in range(0,len(w),2)]correspond à 165 octets, tandis que le vôtre à C=lambda:…;w=C('…')179 octets.
kennytm
@KennyTM Oh, merci. J'ai tellement bricolé avec le code, essayant de respecter la limite de caractères que j'avais perdu le fil de toutes les compressions. : P
Sp3000
4

Python 2, 535 caractères, score de 0.330910, prédit 11.35

Effectuez une moyenne du score des titres contenant chaque mot, puis utilisez les 50 premiers et derniers mots pour éventuellement modifier le score BASE de la guess(title)fonction.

Code Python:

BASE = 11.35
word={}
for sc,ti in csv.reader(open(sys.argv[1])):
    if sc == 'Score': continue
    parts = re.findall(r"[-a-zA-Z']+", ti.lower())
    for p in parts:
        a, c = word.get(p, (0.0,0))
        word[p] = (a+int(sc), c+1)

rank = sorted([(sc/ct,wd) for wd,(sc,ct) in word.items() if ct > 2])
adjust = rank[:50] + rank[-50:]

def guess(title):
    score = BASE
    parts = re.findall(r"[-a-zA-Z']+", title.lower())
    for sc,wd in adjust:
        if wd in parts:
            score *= 0.7 + 0.3*sc/BASE
    return score
Logic Knight
la source
3

C

Score: Inconnu
Longueur: 5 octets
Prédits: 5

Golfé:

int s(char *p){return 5;}

Ungolfed:

int s(char *p)
{
   return 5;
}

Une interrogation des scores donne un score moyen de 5.

Je n'ai pas la possibilité de le tester pour le moment, les autres sont invités à exécuter / modifier.

Joshpbarron
la source
En outre Gulfed: int s () {retour 5;}
Joshua
"Par la présente, vous êtes mis au défi d'écrire un programme ou une fonction prenant comme entrée le titre d'une question PPCG et renvoyant une prédiction de son score." - Desole, mais non: 0
Joshpbarron
J'ai vu une plate-forme selon laquelle si vous oubliez main (), votre première fonction était main (). Peut-être qu'il en dépend.
Josué