ROC Stories


Вступ

ROCStories — це прості історії з чотирьох речень. У тестовому наборі до кожної історії додаються два речення, одне з яких є осмисленим продовженням історії, тоді як друге таким не є. Наприклад:

Karen was assigned a roommate her first year of college.
Her roommate asked her to go to a nearby city for a concert.
Karen agreed happily. The show was absolutely exhilarating.

Тренувальні дані містять п'ять речень (історія та її правильне продовження). Існує файл 100KStories.csv (див. також файл 100KStories.zip) з 98'167 історіями, сумарно з 4'859'629 токенами і 36'566 унікальними словоформами.
Для порівняння один том "Війни і мир" містить близько 150'000 токенів, а wikipedia кілька мільярдів.


Завантаження історій

Оскільки датасет знаходиться в csv-файлі, скористаємося бібліотекою pandas. Історії будемо зберігати в списку docs. Кожна тренувальна історія є списком з 5 речень. Перші дві колонки csv файлу є службовими:

import re                                                        # регулярні вирази
import pandas as pd                                              # csv-файли

df = pd.read_csv('100KStories.zip', sep=',')                     # прочитати з zip-файлу 

docs = []                                                        # список історій 
for i in range( len(df) ):                        
    sents = []                                                   # список речень
    for j in range(5):
        sents.append( preprocessing( df.iloc[i,2+j]) )           # додаємо речення
    docs.append(sents)                                           # додаємо історію

Перед збереженням речень робиться невеликий препроцесинг: у функції preprocess викидаються специфічні пробіли, лапки, переводиться все в нижній регістр і знаки пунктуації відокремлюються від слів:

def preprocess(s):
    s = s.translate( {ord(c): ' ' for c in "\u202f\u200b\xa0"} ) # різні пробіли в ' '
    s = s.translate( {ord(c): ' ' for c in "\"«»"} )             # лапки в ' '
    s = re.sub( '\s+', ' ', s).strip()                           # багато пробілів в один 
    s = s.lower()                                                # в нижній регістр
    
    res = []
    for i, ch in enumerate(s):                                   # відокремлюємо пунктуацію
        if ch in ".,:;!?…%" and s[i-1] != ' ': res.append(' '+ch)
        else:                                  res.append(ch)            
            
    return (' '+ "".join(res)+' ' )                              # для пошуку типу ' cat '

Словник слів

Для складання словника скористаємося об'єктом Counter зі стандартної бібліотеки collections:
from collections import Counter

words = [w for d in docs for s in d for w in s.split()] 
cnt    = Counter(words)          # словник   
tokens = len(wrds)               # всього слів у тексті               
У словнику wordID будемо зберігати номер слова (id) і число його появ на мільйон слів (pm), зберігаючи лише найчастіші слова, що зустрілися не менше 12 разів:
V_DIM  = sum( v >= 12 for v in cnt.values() ) # зустрілися не менше 12 разів
wordID = dict( cnt.most_common(V_DIM) )       # беремо V_DIM найчастіших слів

for i,w in enumerate(wordID):
    wordID[w] = {"id": i,  "pm": int(100000000*(cnt[w]/tokens))/100 }
У ROCStories виходить 10'393 таких слів. Нижче наведено перші 160 слів із зазначенням їхньої частоти pm:
.   97442  with    5202   as      2666  some   1784  bought  1414  put     1117  thought 947  because  826
the 43773  that    4999   an      2562  found  1775  started 1411  family  1107  really  947  game     825
to  34919  up      4792   very    2541  tom    1725  house   1392  no      1089  like    939  buy      820
a   28664  's      4635   them    2435  into   1708  first   1373  just    1086  lot     911  class    814
was 24241  out     4540   not     2289  made   1705  down    1373  always  1079  happy   899  how      810
he  23352  him     4455   home    2273  school 1702  did     1313  looked  1077  has     894  find     806
she 19702  my      4454   from    2216  told   1608  night   1308  money   1044  while   893  great    802
and 19082  one     4322   after   2201  work   1600  finally 1275  said    1039  away    891  ran      797
her 14894  went    4261   we      2178  then   1594  came    1261  make    1036  before  891  john     794
,   14208  day     4183   get     2120  friend 1578  tried   1230  man     1020  gave    880  began    792
his 13346  when    3922   time    2110  car    1567  asked   1227  more    1017  much    874  parents  782
it  12458  but     3892   would   2085  me     1526  off     1201  going   1013  play    866  realized 779
i   10552  got     3884   is      2076  back   1521  never   1186  good     997  what    866  old      778
in  10470  decided 3665   go      1999  could  1514  store   1176  job      989  tim     866  food     771
of   9692  all     3389   took    1969  have   1504  this    1171  next     986  called  863  hard     749
for  8922  were    3307   there   1935  over   1463  mom     1171  needed   976  take    861  mother   744
had  8828  !       3141   be      1919  their  1463  now     1143  couldn't 964  two     859  again    730
on   7407  so      3035   didn't  1918  saw    1444  dog     1134  every    960  do      855  other    725
they 6570  wanted  3016   friends 1900  by     1439  been    1120  too      957  left    834  last     715
at   5413  new     2776   about   1845  loved  1414  felt    1118  see      950  around  834  wasn't   712
Список останніх (рідкісних слів) з pm=2.46:
sparked, olives, stance, 36, badminton,conclusion, pursuing, reddit, mattered, hitter, carole, 
blooms, charlene, headband, wander, mushy, otto, loretta, moses, mildly, hummus, woody, freya
При 4.86 мільйонах токенів pm=2.46 означає, що слово в корпусі зустрілося 12 = 2.46*4.86 разів. Так, слово zeus (lily has two white mice , zeus and zeke .) з pm=2.46 зустрілося в чотирьох історіях 12 разів.

Ентропія словника історій дорівнює 6.023. Порівняно з більшими корпусами текстів існує перекіс на імена людей (історії про певних томів, ліз тощо). Частоти слів, як і належить, задовольняють закон Ципфа. Для перших 1000 слів частота спадає як $i^{-1.07},$
де $i$ — номер за порядком спадання частоти. Для рідкісніших слів частота спадає швидше.
Нижче на графіках у логарифмічному масштабі (по обох осях) намальована залежність $\mathrm{pm}(i)$: