A cebola, ou não a cebola?

The Onion (aviso: muitos artigos são NSFW) é uma organização de notícias satíricas que paródia da mídia tradicional. Em 2014, The Onion lançou o ClickHole (aviso: também frequentemente NSFW), um site de notícias satíricas que parodia sites “clickbait” como o BuzzFeed. Graças à Lei de Poe , é bastante comum as pessoas lerem as manchetes de artigos do The Onion ou ClickHole e acreditarem que são verdadeiras, sem saber que pretendem ser sátira. O contrário também acontece com notícias reais que parecem ridículas – as pessoas muitas vezes pensam que são sátiras, quando não o são.

Essa confusão naturalmente se presta a um jogo – dada a manchete de uma notícia, tente adivinhar se ou não é sátira. Este desafio é fazer exatamente isso com um programa.

Dada uma manchete de notícias (uma string consistindo apenas em caracteres ASCII imprimíveis e espaços), a saída 1 se o o título é uma sátira ou 0 se não for. Sua pontuação será o número de resultados corretos dividido pelo número total de títulos.

Como de costume, brechas padrão (especialmente otimizar para os casos de teste ) não são permitidos. Para garantir isso, executarei seus programas em um conjunto de 200 casos de teste ocultos (100 do The Onion, 100 do Not The Onion). Sua solução deve pontuar no máximo 20 pontos percentuais a menos que sua pontuação nos casos de teste públicos para ser válida.

Casos de teste

Para apresentar casos de teste para este desafio , Eu escolhi 25 títulos do subreddit do The Onion (onde artigos do The Onion e seus sites filhos, como ClickHole, são postados) e 25 títulos do o subreddit Not The Onion (onde são publicados artigos de notícias reais que parecem sátiras). As únicas alterações que fiz nas manchetes foram substituir aspas “extravagantes” por aspas regulares ASCII e padronizar a capitalização – todo o resto é mantido inalterado em relação ao título do artigo original. Cada título está em sua própria linha.

Os títulos do Onion

Trump Warns Removing Confederate Statues Could Be Slippery Slope To Eliminating Racism Entirely "No Way To Prevent This," Says Only Nation Where This Regularly Happens My Doctor Told Me I Should Vaccinate My Children, But Then Someone Much Louder Than My Doctor Told Me I Shouldn"t Man At Park Who Set Up Table Full Of Water Cups Has No Idea How Passing Marathon Runners Got Impression They Can Take Them This Child Would Have Turned 6 Today If His Mother Hadn"t Given Birth To Him In October Incredible Realism: The Campaign In The Next "Call Of Duty" Will Begin At Your Avatar"s High School Cafeteria When He"s Being Tricked Into Joining The Military By A Recruiter "Sometimes Things Have To Get Worse Before They Get Better," Says Man Who Accidentally Turned Shower Knob Wrong Way Report: Uttering Phrase "Easy Does It" Prevents 78% Of Drywall Damage While Moving Furniture Barbara Bush Passes Away Surrounded By Loved Ones, Jeb Family Has Way Too Many Daughters For Them Not To Have Been Trying For Son News: Privacy Win! Facebook Is Adding A "Protect My Data" Button That Does Nothing But Feels Good To Press Dalai Lama Announces Next Life To Be His Last Before Retirement Researchers Find Decline In Facebook Use Could Be Directly Linked To Desire To Be Happy, Fully Functioning Person Manager Of Combination Taco Bell/KFC Secretly Considers It Mostly A Taco Bell Trump: "It"s My Honor To Deliver The First-Ever State Of The Union" Daring To Dream: Jeff Bezos Is Standing Outside A Guitar Center Gazing Longingly At A $200 Billion Guitar Area Dad Looking To Get Average Phone Call With Adult Son Down To 47.5 Seconds Experts Warn Beef Could Act As Gateway Meat To Human Flesh Jeff Bezos Named Amazon Employee Of The Month Dad Suggests Arriving At Airport 14 Hours Early Report: Only 3% Of Conversations Actually Need To Happen Delta Pilot Refuses To Land Until Gun Control Legislation Passed Family Wishes Dad Could Find Healthier Way To Express Emotions Than Bursting Into Full-Blown Musical Number New Honda Commercial Openly Says Your Kids Will Die In A Car Crash If You Buy A Different Brand Teacher Frustrated No One In Beginner Yoga Class Can Focus Chakras Into Energy Blast 

Manchetes Não The Onion

Man Rescued From Taliban Didn"t Believe Donald Trump Was President Nat Geo Hires Jeff Goldblum To Walk Around, Being Professionally Fascinated By Things Mike Pence Once Ratted Out His Fraternity Brothers For Having A Keg Reddit CEO Tells User, "We Are Not The Thought Police," Then Suspends That User Trump Dedicates Golf Trophy To Hurricane Victims Uber"s Search For A Female CEO Has Been Narrowed Down To 3 Men ICE Director: ICE Can"t Be Compared To Nazis Since We"re Just Following Orders Passenger Turned Away From Two Flights After Wearing 10 Layers Of Clothing To Avoid Luggage Fee Somali Militant Group Al-Shabaab Announces Ban On Single-Use Plastic Bags UPS Loses Family"s $846k Inheritance, Offers To Refund $32 Shipping Fee Teen Suspended From High School After Her Anti-Bullying Video Hurts Principal"s Feelings Alabama Lawmaker: We Shouldn"t Arm Teachers Because Most Are Women Cat Named After Notorious B.I.G. Shot Multiple Times - And Survives EPA Head Says He Needs To Fly First Class Because People Are Mean To Him In Coach Apology After Japanese Train Departs 20 Seconds Early Justin Bieber Banned From China In Order To "Purify" Nation Alcohol Level In Air At Fraternity Party Registers On Breathalyzer NPR Tweets The Declaration Of Independence, And People Freak Out About A "Revolution" Man Who Mowed Lawn With Tornado Behind Him Says He "Was Keeping An Eye On It." After Eating Chipotle For 500 Days, An Ohio Man Says He"s Ready For Something New "El Chapo" Promises Not To Kill Any Jurors From Upcoming Federal Trial After 4th DWI, Man Argues Legal Limit Discriminates Against Alcoholics Palestinian Judge Bans Divorce During Ramadan Because "People Make Hasty Decisions When They"re Hungry" Argentinian Officers Fired After Claiming Mice Ate Half A Ton Of Missing Marijuana "Nobody Kill Anybody": Murder-Free Weekend Urged In Baltimore 

Comentários

  • Your score will be the number of correct outputs divided by the total number of headlines Bytecount é um desempate?
  • Eu ‘ um pouco confuso. Que tipo de solução você espera? Cada solução terá que ” otimizar para os casos de teste ” de alguma forma, impede a escrita de uma IA que possa entender inglês e tenha senso de humor. Por exemplo, a solução de Arnauld ‘ detecta /ly\b/ que só funciona porque o 25 Onio Acontece que os títulos que você escolheu têm mais advérbios, mas pelo que sei, você poderia facilmente invadi-los com uma bateria de teste diferente. E quem ‘ disse que seus coeficientes não foram ‘ escolhidos para otimizar sua pontuação? (Por que ‘ ele não os otimizaria?)
  • Esta bateria de teste parece um pouco incomum. É ‘ como pedir um classificador que possa detectar cães em uma fotografia, mas pegar seus casos de teste positivos como fotos de cães e seus casos de teste negativos de um artigo do Buzzfeed intitulado ” 25 fotos de objetos que você ‘ jura que são cães, mas não, parece que eles são ‘ t! (# 11 Vai explodir sua mente!) ” Isso torna um problema bastante difícil ainda mais difícil.
  • O desafio não é apenas difícil, mas ‘ s também não é óbvio (para mim) qual ‘ é a diferença. Se eu não ‘ resolver, é claro que meu programa não ‘ resolverá (isto é, enquanto me convence de que não ‘ t hardcode para os casos de teste)
  • Bem, eu gastei 36 horas treinando uma rede neural artificial usando brain.js e LSTM, com exemplos nesta edição e 100 outros exemplos de cada tipo de links fornecidos, mas o resultado não foi ‘ t bom o suficiente com novos títulos que não eram ‘ t presente em conjuntos de treinamento. Eu ‘ terminei: P

Resposta

JavaScript ( ES7), 39/50 (78%)

63,5% (127/200) em casos de teste ocultos

Uma heurística simples baseada no comprimento do título, número de espaços e uso do sufixo -ly.

 isOnion = str => str.length ** 0.25 + str.split(" ").length ** 1.25 * 2 + str.split(/ly\b/).length ** 1.75 * 7 > 76  

Experimente online!

Comentários

  • Isso é absurdamente eficaz por ser simples.
  • Essa solução teve pontuação de 63,5% em os casos de teste ocultos, por isso é válido.
  • Não tão simples quanto era possível no início do sandbox (100%, utilizando diferenças de capitalização antes de ser padronizado), mas isso é realmente simples.
  • @Mego Só por curiosidade, esta versão NSFW melhora a pontuação nos casos de teste ocultos? 🙂
  • @Arnauld 66% com essa versão

Resposta

Python 3, 84%

Não testado em casos de teste ocultos.

Isso usa Keras LSTM RNN treinado em várias manchetes. Para executá-lo, você precisa do seguinte Keras e do modelo que disponibilizei no GitHub: link do repo . Você precisará do modelo .h5 e os mapeamentos de palavra / vetor estão em .pkl. Os mais recentes

As dependências são:

import numpy as np from pickle import load from keras.preprocessing import sequence, text from keras.models import Sequential from keras.layers import Dense, Embedding, SpatialDropout1D, LSTM, Dropout from keras.regularizers import l2 import re 

As configurações são:

max_headline_length = 70 word_count = 20740 

O modelo é:

model = Sequential() model.add(Embedding(word_count, 32, input_length=max_headline_length)) model.add(SpatialDropout1D(0.4)) model.add(LSTM(64, kernel_regularizer=l2(0.005), dropout=0.3, recurrent_dropout=0.3)) model.add(Dropout(0.5)) model.add(Dense(32, kernel_regularizer=l2(0.005))) model.add(Dropout(0.5)) model.add(Dense(2, kernel_regularizer=l2(0.001), activation="softmax")) 

Agora, para carregar o modelo e a palavra embeddings:

model.load_weights("model.h5") word_to_index = load(open("words.pkl", "rb")) 

E o código para testar se uma string é de “NotTheOnion” ou “TheOnion “Escrevi uma função auxiliar rápida que converte a string para os respectivos embeddings de palavras:

def get_words(string): words = [] for word in re.finditer("[a-z]+|[\"".;/!?]", string.lower()): words.append(word.group(0)) return words def words_to_indexes(words): return [word_to_index.get(word, 0) for word in words] def format_input(word_indexes): return sequence.pad_sequences([word_indexes], maxlen=max_headline_length)[0] def get_type(string): words = words_to_indexes(get_words(string)) result = model.predict(np.array([format_input(words)]))[0] if result[0] > result[1]: site = "NotTheOnion" else: site = "TheOnion" return site 

Explicação

Este código executa um modelo que analisa as relações entre palavras, representando as palavras como um “vetor”. Você pode aprender mais sobre incorporação de palavras aqui .

Isso é treinado em títulos, mas os casos de teste são excluídos .

Este processo é automatizado após bastante processamento. Distribuí a lista final de palavras processadas como .pkl, mas o que acontece na incorporação de palavras é primeiro analisarmos a frase e isolarmos as palavras.

Depois de agora ter as palavras, a próxima etapa é ser capaz de entender as diferenças e semelhanças entre certas palavras, por exemplo, king e queen versus duke e duchess. Esses embeddings não acontecem entre as palavras reais, mas entre os números que representam as palavras que são armazenados no .pkl arquivo. Palavras que a máquina não entende são mapeadas para uma palavra especial <UNK> que nos permite entender que existe uma palavra ali, mas que não se sabe exatamente qual é o significado.

Agora que as palavras podem ser compreendidas, a sequência de palavras (título) precisa ser analisada. Isso é o que “LSTM” faz, um LTSM é um tipo de célula “RNN” que evita o efeito de gradiente de desaparecimento. De forma mais simples, leva em uma sequência de palavras e nos permite encontrar relações entre elas.

Agora, a camada final é Dense o que basicamente significa que é uma espécie de array, o que significa que a saída é como: [probability_is_not_onion, probability_is_onion]. Ao descobrir qual é o maior, podemos escolher qual é o resultado mais confiável para o título fornecido.

Resposta

Python 3 + Keras, 41/50 = 82%

83% (166/200) em casos de teste ocultos

import json import keras import numpy import re from keras import backend as K STRIP_PUNCTUATION = re.compile(r"[^a-z0-9 ]+") class AttentionWeightedAverage(keras.engine.Layer): def __init__(self, return_attention=False, **kwargs): self.init = keras.initializers.get("uniform") self.supports_masking = True self.return_attention = return_attention super(AttentionWeightedAverage, self).__init__(**kwargs) def build(self, input_shape): self.input_spec = [keras.engine.InputSpec(ndim=3)] assert len(input_shape) == 3 self.W = self.add_weight(shape=(input_shape[2], 1), name="{}_W".format(self.name), initializer=self.init) self.trainable_weights = [self.W] super(AttentionWeightedAverage, self).build(input_shape) def call(self, x, mask=None): logits = K.dot(x, self.W) x_shape = K.shape(x) logits = K.reshape(logits, (x_shape[0], x_shape[1])) ai = K.exp(logits - K.max(logits, axis=-1, keepdims=True)) if mask is not None: mask = K.cast(mask, K.floatx()) ai = ai * mask att_weights = ai / (K.sum(ai, axis=1, keepdims=True) + K.epsilon()) weighted_input = x * K.expand_dims(att_weights) result = K.sum(weighted_input, axis=1) if self.return_attention: return [result, att_weights] return result def get_output_shape_for(self, input_shape): return self.compute_output_shape(input_shape) def compute_output_shape(self, input_shape): output_len = input_shape[2] if self.return_attention: return [(input_shape[0], output_len), (input_shape[0], input_shape[1])] return (input_shape[0], output_len) def compute_mask(self, input, input_mask=None): if isinstance(input_mask, list): return [None] * len(input_mask) else: return None if __name__ == "__main__": model = keras.models.load_model("combined.h5", custom_objects={"AttentionWeightedAverage": AttentionWeightedAverage}) with open("vocabulary.json", "r") as fh: vocab = json.load(fh) while True: try: headline = input() except EOFError: break tokens = STRIP_PUNCTUATION.sub("", headline.lower()).split() inp = numpy.zeros((1, 45)) for i, token in enumerate(tokens): try: inp[0,i] = vocab[token] except KeyError: inp[0,i] = 1 print(model.predict(inp)[0][0] > 0.3) 

combined.h5 e vocabulary.json podem ser recuperados aqui (muito grande) e aqui .

Classificador totalmente conectado conectado a um modelo de análise de sentimento pré-treinado DeepMoji, que consiste em LSTMs bidirecionais empilhados e um mecanismo de atenção. Eu congelei as camadas DeepMoji e tirei a camada softmax final, treinei apenas as camadas totalmente conectadas, então descongelei as camadas DeepMoji e as treinei juntas para ajuste fino. O mecanismo de atenção é retirado de https://github.com/bfelbo/DeepMoji/blob/master/deepmoji/attlayer.py (eu não queria ter que usar todo o seu código como uma dependência para uma classe, especialmente porque é Python 2 e bastante difícil de usar como um módulo …)

Isso tem um desempenho surpreendentemente ruim no conjunto de teste do Mego, considerando que em meu próprio conjunto de validação maior obtém> 90%. Então, ainda não terminei com isso.

Comentários

  • 83% em casos de teste ocultos, presumindo que o executei corretamente

Resposta

JavaScript ( Node.js ), 98% (49/50)

96% (192/200) em casos de teste ocultos

 const words = require("./words"); const bags = require("./bags"); let W = s => s.replace(/[^A-Za-z0-9 ]/g, "").toLowerCase().split(" ").filter(w => w.length > 3); let M = b => { for (let i = 0; i < bags.length; i++) { let f = true; for (let j = 0; j < bags[i].length; j++) if (!b.includes(bags[i][j])) { f = false; break; } if (f) return true; } return false; }; let O = s => { let b = []; W(s).forEach(w => { let p = words.indexOf(w); if (p >= 0) b.push(p); }); return (b.length > 0 && M(b)); };  

Isso requer dois grandes arquivos JSON, que não posso “colocá-los aqui ou no ” TiO “. Faça download deles nos links a seguir e salve-os com words.json e nomes, na mesma pasta do arquivo JS. Há também um link para um arquivo JS com casos de teste e resultado / impressão percentual.Você pode colocar seus casos de teste ocultos em variáveis onions e nonOnions.

Depois de salvar todos os 3 arquivos no mesmo diretório, execute node onion.js.

A função O retornará true se for cebola e false se não for. Usa uma grande lista de pacotes de palavras (sem ordem) para detectar se a string de entrada é cebola. Tipo de código rígido, mas funciona muito bem em uma variedade de testes aleatórios casos.

Comentários

  • Esta solução obtém 96% nos casos de teste ocultos

Resposta

Trabalhando na solução de Arnauld “

JavaScript (ES6), 41/50

64% (128/200) em oculto casos de teste

str.includes("Dad") || str.length ** .25 + str.split(" ").length ** 1.25 * 2 + str.split(/ly\b/).length ** 1.75 * 7 > 76 

JavaScript (ES6), 42/50

62,5% (125/200) em casos de teste ocultos (inválido)

isOnion = str => str.includes("Dad") || str.length ** .25 + str.split(" ").length ** 1.25 * 2 + str.split(" ").filter(w => w.length > 3 && w.split(/ly/).length > 1).length * 23.54 + /\d/.test(str) * 8 > 76 

O comprimento + contagem de palavras + “ly “o conceito funciona muito bem, consegui extrair mais alguns pontos ao verificar a palavra” pai “(quando os artigos reais falam sobre os pais das pessoas na terceira pessoa do título?) e um ponto adicional ao alterar a heurística de pesquisa “ly” e verificação da presença de números no título (o que pode ser menos válido no caso geral fora do teste, então deixei as duas soluções)

Comentários

  • Eu não ‘ não sei sobre a parte do pai … parece um pouco como otimizar o caso de teste para mim …
  • E sim, posso encontrar muitos artigos do Not the Onion mencionando pais
  • Há ‘ provavelmente uma maneira melhor de fazer isso como parte da heurística e não apenas um difícil ” win ” se contiver pai, mas imagino que até mesmo fora do banco de dados de teste falando abstratamente sobre um ” Dad ” é mais comum no The Onion
  • Sua primeira solução marcou 64% nos casos de teste ocultos, por isso é válida. Sua segunda solução obteve 62,5% nos casos de teste ocultos, então não é válida.
  • @Mego Que margem próxima …

Deixe uma resposta

O seu endereço de email não será publicado. Campos obrigatórios marcados com *