Бонус. Пример разметки по частям речи

Понятие разметки по частям речи

Здравствуйте и вновь добро пожаловать на занятия по теме «Машинное обучение без учителя: скрытые марковские модели на языке Python» .

В нескольких следующих статьях мы охватим ряд классических проблем обработки естественного языка и обсудим, как нейронные сети могут помочь их решить.

Первой из них является определение частей речи. Разметка по частям речи – это определение категории, к которой принадлежит слово исходя из его синтаксической функции. В английском языке главным частями речи являются существительное, местоимение, прилагательное, глагол, наречие, предлог, союз, междометие. Это не курс по английскому языку, но если вы его хоть в некоторой степени изучали, что должны были сделать для понимания данного курса, то это было в школе, другими словами, вероятно, очень давно. Но даже если вам тяжело в данной области, это не должно оказаться помехой. Не забывайте, машинное обучение предназначено для абстрагированного решения задач. Если вы рассуждаете об изучаемых моделях не в общем, а применительно к конкретным случаям, то польза от вас как специалиста по обработке данных будет весьма ограниченной.

Итак, как же мы будем решать эту задачу? Для начала рассмотрим наши данные. Данные вы можете получить по адресу http://www.cnts.ua.ac.be/conll2000/chunking/. Вы должны скачать файлы train.txt и test.txt и перенести в папку chunking. На слайде показан пример наших данных.

Каждый токен находится в отдельной строке с меткой возле него. Нас интересуют первые два столбца. Как вы можете видеть, каждое слово имеет соответствующую метку.

Когда мы сталкиваемся с задачей машинного обучения, полезно отталкиваться от базового уровня. Какую мы знаем простейшую модель, пригодную для классификации? Логистическую регрессию! Вы можете убедиться, как легко логистическая регрессия может быть применена для нашей задачи: необходимо просто использовать прямое кодирование (one-hot encoding) для слов и меток, после чего использовать функцию софтмакс. При этом можно добиться очень хороших результатов и точности более 90%. Это значит, что более чем в 90% случаев слово имеет ту же метку, что и указанная моделью. Редко когда одно и то же слово может быть использовано в двух разных смыслах, например «Я собираюсь печь пироги» и «Печь сильно дымит».

Мы также вычислим F-меру, которая равна среднему гармоническому значений точности и полноты, являющихся, в свою очередь, общими показателями в исправлении информации.

У вас может возникнуть вопрос: можем ли мы воспользоваться последовательностью, то есть контекстом, для создания лучшего прогноза модели? Замечательнейшим инструментом для этого могут служить рекуррентные нейронные сети, которые мы рассматривали в «Глубоком обучении, часть 5». Заложив базовый уровень, мы посмотрим, как рекуррентные нейронные сети справляются с этой задачей.

Но и это ещё не все. В курсе, предваряющем курс по рекуррентным нейронным сетям, вы изучили скрытые марковские модели. В частности, слова можно рассматривать как наблюдаемую последовательность, а метки – как последовательность скрытых состояний, а потому скрытые марковские модели кажутся отличным решением данной задачи. Впрочем, есть одна маленькая загвоздка. Если вы изучали курс по скрытым марковским моделям, то помните, что для обучения скрытой марковской модели используется так называемый алгоритм максимизации ожиданий. Как вы думаете, здесь он нужен? Ответ: нет, конечно же нет! В данном случае мы находимся в той интересной ситуации, когда на самом деле знаем скрытые состояния обучающих данных. Это один из тех случаев, когда скрытые состояния являются не какими-то абстрактными гиперпараметрами, а вполне реальной, осязаемой вещью.

Напомним, что скрытая марковская модель определяется тремя параметрами: π – начальным распределением состояний, A – матрицей перехода состояний, и B – матрицей вероятностей наблюдений. Все эти вещи мы можем вычислить напрямую, используя максимум функции правдоподобия. К примеру, π будет просто частотой начальных меток. A, как мы знаем, подчиняется марковскому правилу, поэтому может быть вычислена путём подсчёта всех переходов и деления на сумму строк. Вероятности же наблюдения зависят только от текущего состояния, поэтому являются лишь вероятностями слова с учётом метки, что мы также можем вычислить путём подсчёта.

Подводя итог, вот как мы можем решить задачу разметки по частям речи. Во-первых, мы используем логистическую регрессию чтобы заложить базу. Во-вторых, мы используем рекуррентные нейронные сети, чтобы оценить, дадут ли рассматриваемые последовательности лучшую точность. И в-третьих, воспользуемся скрытыми марковскими моделями, также работающими с последовательностями, чтобы оценить, может ли простой генеративный алгоритм дать ещё лучшую точность.

Разметка по частям речи с помощью скрытой марковской модели

Итак, начнём с импорта Numpy и Matplotlib. Кроме того, импортируем os и sys, поскольку будем использовать код, уже написанный на предыдущих занятиях. Из курса по скрытым марковским моделям импортируем HMM, но особо не беспокойтесь, если вы его ещё не изучали. И, разумеется, импортируем функцию get_data из pos_baseline.

import numpy as np

import matplotlib.pyplot as plt

import os

import sys

sys.path.append(os.path.abspath(‘..’))

from hmm_class.hmmd_scaled import HMM

from pos_baseline import get_data

from sklearn.utils import shuffle

from datetime import datetime

from sklearn.metrics import f1_score

Нам нужна функция для вычисления точности, поскольку нет никакого реального способа проверить правильность скрытых состояний.

def accuracy(T, Y):

    n_correct = 0

    n_total = 0

    for t, y in zip(T, Y):

        n_correct += np.sum(t == y)

        n_total += len(y)

    return float(n_correct) / n_total

Разумеется, мы также вычислим F-меру.

def total_f1_score(T, Y):

    T = np.concatenate(T)

    Y = np.concatenate(Y)

    return f1_score(T, Y, average=None).mean()

И напишем функцию main. В ней будет параметр сглаживания – для сглаживания наших матрицы перехода состояний и матрицы наблюдений.

def main(smoothing=10e-2):

    Xtrain, Ytrain, Xtest, Ytest, word2idx = get_data(split_sequences=True)

    V = len(word2idx) + 1

    M = max(max(y) for y in Ytrain) + 1

    A = np.ones((M, M))*smoothing

    pi = np.zeros(M)

    for y in Ytrain:

        pi[y[0]] += 1

        for i in xrange(len(y)-1):

            A[y[i], y[i+1]] += 1

Разобравшись с циклом, нормализуем все распределения, чтобы их сумма равнялась единице.

    A /= A.sum(axis=1, keepdims=True)

    pi /= pi.sum()

Теперь B.

    B = np.ones((M, V))*smoothing

    for x, y in zip(Xtrain, Ytrain):

        for xi, yi in zip(x, y):

            B[yi, xi] += 1

    B /= B.sum(axis=1, keepdims=True)

    hmm = HMM(M)

    hmm.pi = pi

    hmm.A = A

    hmm.B = B

Следующий этап – создание прогноза. Для этого у нас уже есть функция get_state_sequence.

    Ptrain = []

    for x in Xtrain:

        p = hmm.get_state_sequence(x)

        Ptrain.append(p)

    Ptest = []

    for x in Xtest:

        p = hmm.get_state_sequence(x)

        Ptest.append(p)

И выводим результат на экран.

    print(“train accuracy:”, accuracy(Ytrain, Ptrain))

    print(“test accuracy:”, accuracy(Ytest, Ptest))

    print(“train f1:”, total_f1_score(Ytrain, Ptrain))

    print(“test f1:”, total_f1_score(Ytest, Ptest))

if __name__ == ‘__main__’:

main()

Запустим программу и проверим результат.

Понравилась статья? Поделить с друзьями:
Добавить комментарий

;-) :| :x :twisted: :smile: :shock: :sad: :roll: :razz: :oops: :o :mrgreen: :lol: :idea: :grin: :evil: :cry: :cool: :arrow: :???: :?: :!: