Лук или не лук?


11

The Onion (предупреждение: многие статьи - NSFW) - это сатирическая новостная организация, пародирующая традиционные новостные СМИ. В 2014 году The Onion запустил ClickHole (предупреждение: также часто NSFW), сатирический новостной сайт, который пародирует такие сайты, как BuzzFeed. Благодаря Закону По , люди довольно часто читают заголовки статей из The Onion или ClickHole и верят, что это правда, не зная, что они предназначены для сатиры. Обратное также происходит с нелепо звучащими реальными новостями - люди часто думают, что они сатиры, а они нет.

Эта путаница естественным образом поддается игре - учитывая заголовок новостей, попытайтесь угадать, сатира или нет. Задача состоит в том, чтобы сделать именно это с помощью программы.

Учитывая заголовок новости (строку, состоящую только из печатаемых символов ASCII и пробелов), выведите, 1если заголовок является сатирическим или 0нет. Ваша оценка будет количеством правильных результатов, разделенных на общее количество заголовков.

Как обычно, стандартные лазейки (особенно оптимизирующие для тестовых случаев ) не допускаются. Чтобы обеспечить это, я буду запускать ваши программы на наборе из 200 скрытых тестовых случаев (100 от The Onion, 100 от Not The Onion). Ваше решение должно набрать не более 20 процентных пунктов меньше, чем ваш результат в общедоступных тестовых случаях, чтобы быть действительным.

Тестовые случаи

Чтобы подготовить контрольные примеры для этой задачи, я выбрал 25 заголовков из субреддита The Onion (где размещены статьи из The Onion и его дочерних сайтов, таких как ClickHole), и 25 заголовков из субреддита Not The Onion (где реальные новостные статьи это звучит как сатира выложены). Единственными изменениями, которые я внес в заголовки, была замена «причудливых» цитат обычными кавычками ASCII и стандартизация заглавных букв - все остальное осталось неизменным по сравнению с заголовком оригинальной статьи. Каждый заголовок находится на отдельной строке.

Луковые заголовки

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

Не луковые заголовки

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 headlinesЯвляется ли bytecount прерывателем связи?
Скидсдев

9
Я немного смущен. Что вид раствора можно ожидать? Каждое решение должно будет несколько «оптимизироваться для тестовых случаев», исключая написание ИИ, который понимает английский и имеет чувство юмора. Например, решение Arnauld обнаруживает, /ly\b/что работает только потому, что у 25 выбранных вами заголовков Onion есть больше наречий, но, насколько я знаю, вы можете легко сработать с другой тестовой батареей. И кто скажет, что его коэффициенты не выбраны, чтобы оптимизировать его счет? (Почему бы ему не оптимизировать их?)
Линн

10
Этот тест батареи кажется немного необычным. Это все равно что запросить классификатор, который может обнаружить собак на фотографии, но взять ваши положительные тестовые случаи в качестве фотографий собак и ваши негативные тестовые случаи из статьи в Buzzfeed под названием «25 фотографий объектов, которые вы поклянетесь, собаки, но нет, повороты» Вне они не! (# 11 взорвет ваш разум!) "Это делает достаточно трудную проблему сложнее.
София Лехнер,

4
Это не только сложная задача, но и неочевидная (для меня) в чем разница. Если я не могу решить ее, конечно, моя программа не может решить ее (то есть убедить меня, что она не жестко
кодирует

4
Ну, я потратил +36 часов на обучение искусственной нейронной сети с использованием brain.jsи LSTM, с примерами в этом выпуске и 100 другими образцами каждого типа по предоставленным ссылкам, но результат был недостаточно хорош с новыми названиями, которых не было в учебных наборах. , Я сделал: P
Night2

Ответы:


7

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

63,5% (127/200) по скрытым тестам

Простая эвристика, основанная на длине заголовка, количестве пробелов и использовании -lyсуффикса.

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

Попробуйте онлайн!


Это абсурдно эффективно, насколько это просто.
Дон Тысяча

Это решение набрало 63,5% в скрытых тестовых случаях, поэтому оно действительно.
Мего

Не так просто, как это было возможно в начале песочницы (100%, с использованием различий в капитализации до того, как она была стандартизирована), но это действительно просто.
Захари

@Mego Просто из любопытства, улучшит ли эта версия NSFW результаты в скрытых тестах? :)
Арно

@Arnauld 66% с этой версией
Mego

6

Python 3, 84%

Не проверено на скрытых тестах.

Для этого используется Keras LSTM RNN, прошедший подготовку по различным заголовкам. Для его запуска вам понадобится Keras, а также модель, которую я сделал доступной на GitHub: ссылка на репо . Вам понадобится модель .h5и отображение слова / вектора .pkl. Последний

Зависимости:

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

Настройки:

max_headline_length = 70
word_count = 20740

Модель является:

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'))

Теперь для загрузки модели и вложения слова:

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

И код для проверки, является ли строка из «NotTheOnion» или «TheOnion», я написал быструю вспомогательную функцию, которая преобразует строку в соответствующие вложения слова:

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

объяснение

Этот код запускает модель, которая анализирует отношения между словами, представляя слова как «вектор». Вы можете узнать больше о встраивании слов здесь .

Это натренировано на заголовки, но тестовые случаи исключены .

Этот процесс автоматизирован после небольшой обработки. Я распространил окончательно обработанный список слов как, .pklно что происходит при встраивании слов, так это сначала мы анализируем предложение и выделяем слова.

После того, как у нас теперь есть слова, следующим шагом будет понимание различий и сходств между определенными словами, например, kingи queenпротив dukeи duchess. Эти вложения происходят не между реальными словами, а между числами, представляющими слова, которые хранятся в .pklфайле. Слова, которые машина не понимает, сопоставляются с особым словом, <UNK>которое позволяет нам понять, что там есть слово, но что точно не известно, в чем смысл.

Теперь, когда слова могут быть поняты, необходимо проанализировать последовательность слов (заголовок). Это то, что делает «LSTM», LTSM - это тип ячейки «RNN», который избегает эффекта исчезающего градиента. Проще говоря, он состоит из последовательности слов и позволяет нам находить отношения между ними.

Теперь последний слой , Denseкоторый в основном означает , что это вроде как массив означает выход, как: [probability_is_not_onion, probability_is_onion]. Найдя, какой из них больше, мы можем выбрать, какой из них является наиболее уверенным результатом для данного заголовка.


3

Питон 3 + Керас, 41/50 = 82%

83% (166/200) на скрытых тестовых случаях

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и vocabulary.jsonможет быть найден здесь (очень большой) и здесь .

Полностью связанный классификатор подключен к предварительно обученной модели анализа настроений DeepMoji, которая состоит из двухсторонних LSTM и механизма внимания. Я заморозил слои DeepMoji и вынул последний слой softmax, натренировал только полностью соединенные слои, затем разморозил слои DeepMoji и собрал их вместе для тонкой настройки. Механизм внимания взят из https://github.com/bfelbo/DeepMoji/blob/master/deepmoji/attlayer.py (я не хотел использовать весь их код в качестве зависимости для одного класса, тем более что Python 2 и довольно громоздкий для использования в качестве модуля ...)

Это на удивление плохо работает на тестовом наборе Mego, учитывая, что в моем большем наборе проверки он получает> 90%. Так что я еще не закончил с этим.


83% на скрытых тестовых случаях, при условии, что я выполнил это правильно
Mego

1

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

96% (192/200) на скрытых тестовых случаях

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));
};

Для этого требуются два больших файла JSON, которые я не могу поместить здесь или в «TiO». Пожалуйста , загрузите их из следующих ссылок и сохранять их с words.jsonи bags.jsonименами, в той же папке, что и JS - файла. Существует также ссылка на файл JS с контрольными примерами и результатами / процентами печати. Вы можете поместить свои скрытые контрольные примеры onionsи nonOnionsпеременные.

После сохранения всех 3 файлов в одном каталоге, запустите node onion.js.

OФункция будет возвращать trueесли лук и falseесли это не так . Использует большой список сумок слов (без порядка), чтобы определить, является ли входная строка луком. Вид жестко закодирован, но очень хорошо работает на различных случайных тестовых случаях.


Это решение получает 96% на скрытых тестовых случаях
Mego

0

Отработка решения Арно

JavaScript (ES6), 41/50

64% (128/200) по скрытым тестам

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) по скрытым тестам (недействительно)

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

Концепция «длина + количество слов» + «ly» работает довольно хорошо, я смог выделить еще несколько моментов, проверив слово «папа» (когда в реальных статьях говорится о папах людей в третьем лице в названии?) И дополнительный пункт, изменив эвристику поиска «ly» и проверив наличие чисел в заголовке (что может быть менее актуально в общем случае вне теста, поэтому я оставил оба решения)


Я не знаю насчет папы ... мне кажется, это немного похоже на оптимизацию тестового примера ...
Дон Тысяча

И да, я могу найти множество статей Не Лука, в которых упоминаются папы
Дон Тысяча

Вероятно, есть лучший способ сделать это как часть эвристического, а не просто жесткого «выигрыша», если в нем содержится папа, но я думаю, что даже за пределами тестовой базы данных абстрактные разговоры о конкретном «папе» чаще встречаются на The Onion
TiKevin83

Ваше первое решение набрало 64% ​​в скрытых тестовых случаях, поэтому оно действительно. Ваше второе решение набрало 62,5% для скрытых тестовых случаев, поэтому оно недействительно.
Мего

@Mego Какая близкая разница ...
user202729
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.