Это четвертый пост в моей серии Последовательность маркировки в Python Найдите предыдущий здесь: Извлечение дополнительных характеристик . Получить код для этой серии на Гадость .
После того, как мы имеем наш набор данных со всеми функциями, которые мы хотим включить, а также все этикетки для наших последовательностей; Мы можем перейти к фактической подготовке нашего алгоритма. Для этой задачи мы будем использовать Python-Crfsuite
Пакет, так что Pip-установите его (или используйте свой любимый менеджер пакета, чтобы получить его):
pip install python-crfsuite
После установки давайте загрузим наш набор набора данных:
import pandas as pd import string features_labels = pd.read_csv("data/features-labels.csv") features_labels = features_labels[~features_labels['label'].isna()] features_labels.head()
Нам также нужен функции помощника, которые работают на струнах; Вы увидите, как они используются немного дальше:
punctuation = set(string.punctuation) def is_punctuation(token): return token in punctuation def is_numeric(token): try: float(token.replace(",", "")) return True except: return False
Вход в pycrfsuite.
Входы в алгоритм должны соблюдать определенный формат, где каждый токен имеет свои функции, представленные пар ключа, каждый токен может также иметь разные функции на основе разных факторов, таких как его положение. Следующая функция принимает в DataFrame и возвращает соответствующие функции, которые могут потребляться методом обучения нашим алгоритмом:
def featurise(sentence_frame, current_idx): current_token = sentence_frame.iloc[current_idx] token = current_token['token'] position = current_token['position'] token_count = current_token['token_count'] pos = current_token['pos_tag'] # Shared features across tokens features = { 'bias': True, 'word.lower': token.lower(), 'word.istitle': token.istitle(), 'word.isdigit': is_numeric(token), 'word.ispunct': is_punctuation(token), 'word.position':position, 'word.token_count': token_count, 'postag': pos, } if current_idx > 0: # The word is not the first one... prev_token = sentence_frame.iloc[current_idx-1]['token'] prev_pos = sentence_frame.iloc[current_idx-1]['pos_tag'] features.update({ '-1:word.lower': prev_token.lower(), '-1:word.istitle':prev_token.istitle(), '-1:word.isdigit': is_numeric(prev_token), '-1:word.ispunct': is_punctuation(prev_token), '-1:postag':prev_pos }) else: features['BOS'] = True if current_idx < len(sentence_frame) - 1: # The word is not the last one... next_token = sentence_frame.iloc[current_idx+1]['token'] next_tag = sentence_frame.iloc[current_idx+1]['pos_tag'] features.update({ '+1:word.lower': next_token.lower(), '+1:word.istitle': next_token.istitle(), '+1:word.isdigit': is_numeric(next_token), '+1:word.ispunct': is_punctuation(next_token), '+1:postag': next_tag }) else: features['EOS'] = True return features featurise(offer_0, 1)
Смешив первый токен первого предложения, мы получаем следующее:
{'bias': True, 'word.lower': 'cun', 'word.istitle': False, 'word.isdigit': False, 'word.ispunct': False, 'word.position': 1, 'word.token_count': 11, 'postag': 'np00000', '-1:word.lower': '¡', '-1:word.istitle': False, '-1:word.isdigit': False, '-1:word.ispunct': False, '-1:postag': 'faa', '+1:word.lower': 'a', '+1:word.istitle': False, '+1:word.isdigit': False, '+1:word.ispunct': False, '+1:postag': 'sp000'}
Как видите, функции представлены в словаре, где ключи могут быть любым словарным ключом, но я решил назвать их так, чтобы облегчить найти, откуда приходит каждое конкретное значение.
Опять же, нам нужны некоторые функции для создания предложений обратно из токенов:
def featurize_sentence(sentence_frame): labels = list(sentence_frame['label'].values) features = [featurize(sentence_frame, i) for i in range(len(sentence_frame))] return features, labels def rollup(dataset): sequences = [] labels = [] offers = dataset.groupby('offer_id') for name, group in offers: sqs, lbls = featurize_sentence(group) sequences.append(sqs) labels.append(lbls) return sequences, labels all_sequences, all_labels = rollup(features_labels)
У нас сейчас есть в All_squences
и all_labels
Наши функции и их соответствующие этикетки готовы к обучению.
Обучение
В значительной степени похоже в любой другой контролируемой проблеме, нам нужно разделить наш набор набора тренировок на два (предпочтительно трех) наборов данных; Мы можем использовать rain_test_split.
для этого:
from sklearn.model_selection import train_test_split train_docs, test_docs, train_labels, test_labels = train_test_split(all_sequences, all_labels) len(train_docs), len(test_docs)
Создание CRF.
Хотя можно использовать kklearn – как Интерфейс для создания, поезда и вывод с Python-Crfsuite, я решил использовать оригинальную упаковку и делать все «вручную».
Первый шаг – создать объект класса Тренер
; Тогда мы можем установить некоторые параметры для фазы тренировок, не стесняйтесь играть с ними, так как они могут улучшить качество теггера. Наконец, нам нужно пройти в наших учебных данных в алгоритме, и мы делаем это с помощью метода Добавить:
import pycrfsuite trainer = pycrfsuite.Trainer(verbose=False) trainer.set_params({ 'c1': 1.0, # coefficient for L1 penalty 'c2': 1e-3, # coefficient for L2 penalty 'max_iterations': 200, 'feature.possible_transitions': True }) # We are feeding our training set to the algorithm here. for xseq, yseq in zip(train_docs, train_labels): trainer.append(xseq, yseq)
Наконец, мы называем поезду метода, который в то же время сохранит модель к файлу, которое мы можем использовать для выполнения выводов в новых предложениях.
trainer.train('model/vuelax-bad.crfsuite')
Маркировка «невидимых» последовательностей
Чтобы выполнить последовательность маркировки в случаях, когда наш алгоритм не видел во время тренировок, необходимо использовать объект Tagger
класс, а затем загрузить нашу сохраненную модель в него, используя открыть
метод.
crf_tagger = pycrfsuite.Tagger() crf_tagger.open('model/vuelax-bad.crfsuite')
Помните, что каждое из предложений необходимо обрабатывать и помещать в формат, необходимый для Tagger для работы, это означает, что имеют те же функции, которые мы использовали для обучения. У нас уже есть это в нашем test_docs
и мы можем использовать их напрямую:
predicted_tags = crf_tagger.tag(test_docs[2]) print("Predicted: ",predicted_tags) print("Correct : ",test_labels[2])
Результатом может показать, что в этом конкретном экземпляре Tagger не ошиблись:
Predicted: ['n', 'o', 's', 'd', 'd', 'n', 'p', 'n'] Correct : ['n', 'o', 's', 'd', 'd', 'n', 'p', 'n']
Оценка теггера
Видя наш алгоритм очень хорошо в одном примере, в одном примере не может сообщить нам о том, что давайте посмотрим на большую картину, и хотя могут быть лучшие способы оценки производительности теггера, мы будем использовать традиционные инструменты проблемы классификации:
from sklearn.metrics import classification_report all_true, all_pred = [], [] for i in range(len(test_docs)): all_true.extend(test_labels[i]) all_pred.extend(crf_tagger.tag(test_docs[i])) print(classification_report(all_true, all_pred))
Должен дать вам результат, похожий на …
precision recall f1-score support d 0.96 0.99 0.97 98 f 1.00 1.00 1.00 10 n 1.00 1.00 1.00 831 o 1.00 1.00 1.00 80 p 1.00 1.00 1.00 60 s 1.00 1.00 1.00 60 accuracy 1.00 1139 macro avg 0.99 1.00 1.00 1139 weighted avg 1.00 1.00 1.00 1139
Наш алгоритм выступает очень, очень хорошо.
Может показаться, что мы здесь сделаем, но нам все еще нужно собрать все вместе, в попытке упростить нам метки новых предложений за пределами наших наборов обучения и тестирования, мы сделаем это в моем следующем посте, положив все вместе .
Как всегда, не стесняйтесь задавать несколько вопросов, если у вас есть их, оставив комментарий здесь или связаться со мной в Twitter через @io_Exception Отказ
Оригинал: “https://dev.to/fferegrino/conditional-random-fields-in-python-sequence-labelling-part-4-5ei2”