Языковые модели на n-граммах
Языковая модель отвечает на вопрос «какое слово вероятнее всего идёт дальше» — и самые первые такие модели строились прямо на n-граммах.
Языковая модель — модель, которая оценивает вероятность последовательности слов и, как следствие, предсказывает следующее слово по предыдущим.
Зачем нужна вероятность текста
Если модель умеет сказать, что «я люблю мороженое» вероятнее, чем «я люблю молоток», она косвенно «понимает» язык. На этом стоит автодополнение, исправление опечаток, распознавание речи (выбрать правдоподобную расшифровку), перевод. И это прямой предок GPT — тот тоже всего лишь предсказывает следующий токен, только неизмеримо мощнее.
Биграммная модель
Точно оценить вероятность длинного предложения невозможно — таких предложений в данных нет. Упрощение: вероятность следующего слова зависит только от одного предыдущего (биграмма). Это марковское допущение.
P(следующее слово | предыдущее) = (сколько раз пара встретилась) / (сколько раз встретилось предыдущее слово)
Строим биграммную модель и предсказываем
from collections import defaultdict, Counter
corpus = (
"я люблю мороженое "
"я люблю котов "
"я люблю мороженое "
"ты любишь котов"
).split()
# bigrams: предыдущее слово -> Counter следующих слов
bigrams = defaultdict(Counter)
for a, b in zip(corpus, corpus[1:]):
bigrams[a][b] += 1
def next_word_probs(word):
counts = bigrams[word]
total = sum(counts.values())
return {w: round(c / total, 3) for w, c in counts.items()}
print("После 'я' :", next_word_probs("я"))
print("После 'люблю':", next_word_probs("люблю"))
Вывод:
После 'я' : {'люблю': 1.0}
После 'люблю': {'мороженое': 0.667, 'котов': 0.333}
Модель выучила из данных: после «я» всегда идёт «люблю», а после «люблю» в два раза чаще «мороженое», чем «котов». Это и есть предсказание следующего слова на вероятностях.
Генерация текста
Раз умеем предсказывать следующее слово, умеем и генерировать: берём слово, смотрим распределение, выбираем следующее, повторяем. Сгенерируем «жадно» — всегда самое вероятное слово.
from collections import defaultdict, Counter
corpus = "я люблю мороженое я люблю котов я люблю мороженое ты любишь котов".split()
bigrams = defaultdict(Counter)
for a, b in zip(corpus, corpus[1:]):
bigrams[a][b] += 1
def generate(start, n):
word = start
result = [word]
for _ in range(n - 1):
if not bigrams[word]:
break
word = bigrams[word].most_common(1)[0][0] # самое вероятное следующее
result.append(word)
return " ".join(result)
print(generate("я", 4))
print(generate("ты", 3))
Вывод:
я люблю мороженое я ты любишь котов
Модель сама породила осмысленные цепочки. Это игрушка, но механизм тот же, что у больших языковых моделей: предсказать следующий токен и продолжить.
Триграммы и компромисс
Биграмма видит только одно предыдущее слово — мало контекста. Триграмма (два предыдущих) точнее, но требует больше данных: троек в корпусе меньше, многие не встречаются ни разу (разрежённость). Чем длиннее контекст n-граммы, тем точнее, но тем острее нехватка данных. Это фундаментальный потолок n-граммных моделей.
Итог
- Языковая модель оценивает вероятность слов и предсказывает следующее.
- Биграммная модель смотрит на одно предыдущее слово (марковское допущение).
- Вероятность = частота пары / частота предыдущего слова.
- Из предсказания рождается генерация текста — прямой предок GPT.
- Длинный контекст точнее, но упирается в нехватку данных.