A cebola, ou não a cebola?


11

The Onion (aviso: muitos artigos são NSFW) é uma organização satírica de notícias que parodia a mídia tradicional. Em 2014, o The Onion lançou o ClickHole (aviso: também freqüentemente NSFW), um site de notícias satíricas que parodia sites " isca de clique " como o BuzzFeed. Graças à Lei de Poe , é bastante comum as pessoas lerem as manchetes dos artigos de The Onion ou ClickHole e acreditarem que são verdadeiras, sem saber que se destinam a ser sátira. O inverso também acontece com notícias reais de som ridículo - as pessoas costumam pensar que são sátiras quando não são.

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

Dada uma manchete de notícias (uma sequência que consiste apenas em caracteres e espaços ASCII imprimíveis), produza 1se a manchete for sátira ou 0se não for. Sua pontuação será o número de resultados corretos dividido pelo número total de títulos.

Como de costume, não são permitidas brechas padrão (especialmente otimizadas para os casos de teste ). Para impor isso, executarei seus programas em um conjunto de 200 casos de teste ocultos (100 da The Onion, 100 da Not The Onion). Sua solução não deve ter mais que 20 pontos percentuais a menos que sua pontuação nos casos de teste públicos para ser válida.

Casos de teste

Para criar casos de teste para esse desafio, escolhi 25 manchetes do subreddit The Onion (onde são publicados artigos do The Onion e seus sites filhos, como ClickHole) e 25 manchetes do subreddit Not The Onion (onde artigos reais de notícias que soam como sátira). As únicas mudanças que fiz nas manchetes foram substituir cotações "sofisticadas" por cotações ASCII regulares e padronizar letras maiúsculas - tudo o mais permanece inalterado em relação ao título do artigo original. Cada título está em sua própria linha.

As manchetes da cebola

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

Não as manchetes da cebola

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

6
Your score will be the number of correct outputs divided by the total number of headlinesO bytecount é um desempate?
Skidsdev 15/08/19

9
Eu estou um pouco confuso. Que tipo de solução você espera? Toda solução terá que "otimizar para os casos de teste" um pouco, impedindo a criação de uma IA que possa entender inglês e tenha senso de humor. Por exemplo, a solução da Arnauld detecta o /ly\b/que funciona apenas porque os 25 títulos da Onion que você escolheu têm mais advérbios, mas, pelo que sei, você pode facilmente tropeçar 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 otimizá-los?)
Lynn

10
Esta bateria de teste parece um pouco incomum. É como pedir um classificador que possa detectar cães em uma fotografia, mas tirar os seus casos de teste positivo como fotos de cães e os negativos de um artigo do Buzzfeed intitulado "25 fotos de objetos que você jura que são cães, mas não, vira Lá fora eles não estão! (# 11 vai explodir sua mente!) "Isso torna um problema difícil o suficiente.
Sophia Lechner

4
O desafio não é apenas difícil, mas também não é óbvio (para mim) qual é a diferença. Se eu não conseguir resolvê-lo, é claro que meu programa não pode resolvê-lo (isto é, enquanto me convencer de que isso não acontece hardcode para os casos de teste)
user202729

4
Passei +36 horas treinando uma rede neural artificial usando brain.jse LSTM, com amostras nesta edição e outras 100 amostras de cada tipo a partir de links fornecidos, mas o resultado não foi bom o suficiente com novos títulos que não estavam presentes nos conjuntos de treinamento . Estou terminado: P
Night2

Respostas:


7

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

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

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

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

Experimente online!


Isso é absurdamente eficaz para quão simples é.
Don Thousand

Essa solução obteve 63,5% nos casos de teste ocultos, portanto é válida.
Mego

Não é o mais simples possível no início do sandbox (100%, utilizando diferenças de capitalização antes de ser padronizado), mas isso é realmente simples.
Zacharý 17/08/19

@Mego Por curiosidade, esta versão do NSFW melhora a pontuação nos casos de teste ocultos? :)
Arnauld

@Arnauld 66% com essa versão
Mego

6

Python 3, 84%

Não testado em casos de teste ocultos.

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

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 incorporação:

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 de ajuda rápida que converte a string nas respectivas inserções 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 os relacionamentos entre as palavras, representando as palavras como um 'vetor'. Você pode aprender mais sobre a incorporação de palavras aqui .

Isso é treinado nas manchetes, mas os casos de teste são excluídos .

Este processo é automatizado após um pouco de processamento. Eu distribuí a lista final de palavras processadas como uma, .pklmas o que acontece na incorporação de palavras é primeiro que analisamos a frase e isolamos as palavras.

Depois de termos as palavras agora, o próximo passo é ser capaz de entender as diferenças e semelhanças entre certas palavras, por exemplo, kinge queenversus dukee duchess. Essas incorporações não acontecem entre as palavras reais, mas entre os números que representam as palavras, que é o que é armazenado no .pklarquivo. As palavras que a máquina não entende são mapeadas para uma palavra especial <UNK>que nos permite entender que existe uma palavra lá, mas que não se sabe exatamente qual é o significado.

Agora que as palavras podem ser entendidas, a sequência de palavras (título) precisa poder ser analisada. É isso que o 'LSTM' faz, um LTSM é um tipo de célula 'RNN' que evita o efeito gradiente de fuga. Mais simplesmente, é preciso uma sequência de palavras e permite encontrar relações entre elas.

Agora, a camada final é Denseque, basicamente, significa que é mais ou menos como uma matriz ou seja, a saída é como: [probability_is_not_onion, probability_is_onion]. Ao descobrir qual é maior, podemos escolher qual é o resultado mais confiante para o título especificado.


3

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.h5e vocabulary.jsonpode ser recuperado daqui (muito grande) e aqui .

Classificador totalmente conectado, conectado a um modelo de análise de sentimentos pré-treinado DeepMoji, que consiste em LSTMs bidirecionais empilhados e um mecanismo atencional. Congelei as camadas do DeepMoji e retirei a camada final do softmax, treinei apenas as camadas totalmente conectadas, depois descongelei as camadas do DeepMoji e as treinei para o ajuste fino. O mecanismo atencional é obtido em https://github.com/bfelbo/DeepMoji/blob/master/deepmoji/attlayer.py (eu não queria usar todo o código deles como dependência de uma classe, especialmente porque é Python 2 e bastante pesado para usar como módulo ...)

Isso apresenta um desempenho surpreendentemente ruim no conjunto de testes do Mego, considerando que, no meu próprio conjunto de validação maior, ele chega a> 90%. Então, eu não terminei com isso ainda.


83% em casos de teste ocultos, supondo que eu o executei corretamente #
Mego

1

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 arquivos JSON grandes que não podem ser colocados aqui ou no "TiO". Faça o download dos seguintes links e salve-os com os nomes words.jsone bags.json, na mesma pasta que o arquivo JS. Também há um link para um arquivo JS com casos de teste e resultado / porcentagem de impressão. Você pode colocar seus casos de teste ocultos em onionse nonOnionsvariáveis.

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

A Ofunção retornará truese for cebola e falsese não for. Usa uma grande lista de sacos de palavras (sem ordem) para detectar se a sequência de entrada é cebola. Tipo de código rígido, mas funciona muito bem em uma variedade de casos de teste aleatórios.


Esta solução obtém 96% nos casos de teste escondidos
Mego

0

Trabalhando com a solução da Arnauld

JavaScript (ES6), 41/50

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

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 conceito de comprimento + contagem de palavras + "ly" funciona muito bem, consegui extrair mais alguns pontos verificando a palavra "pai" (quando artigos reais falam sobre os pais das pessoas na terceira pessoa no título?) E um ponto adicional, alterando a heurística da pesquisa "ly" e verificando a presença de números no título (que pode ser menos válido no caso geral fora do teste, deixei as duas soluções)


Eu não sei sobre a parte do pai ... parece um pouco como otimizar o caso de teste para mim ...
Don Thousand

E sim, posso encontrar muitos artigos de Not the Onion mencionando pais #
Don Thousand

Provavelmente existe uma maneira melhor de fazê-lo como parte da heurística e não apenas uma "vitória" difícil se contiver pai, mas imagino que mesmo fora do banco de dados de testes, falar abstratamente sobre um "pai" específico é mais comum em The Onion
TiKevin83

Sua primeira solução obteve 64% nos casos de teste ocultos, portanto é válida. Sua segunda solução obteve 62,5% nos casos de teste ocultos, portanto não é válida.
Mego

@Mego O que uma margem de perto ...
user202729
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.