Как фанат НЛП, я всегда задавался вопросом, как Google Ассистент или Алекса понимают, когда я просил его что-то сделать. Вопрос продолжался, могу ли я заставить мою машину тоже меня понимать? Решением было - Классификация намерений.
Классификация намерений - это часть Natural Language Understanding, где алгоритм машинного обучения / глубокого обучения учится классифицировать данную фразу на основе тех, на которых она была обучена.
Возьмем забавный пример. Я делаю ассистента, как Алекса.
Для простоты мы возьмем 3 задачи: включим свет, выключим его и расскажем, какая сейчас погода. Дадим названия всем трем задачам TurnOnLights, TurnOffLights и Weather. Все эти задачи в NLU называются «намерениями». Другими словами, намерение - это группа похожих фраз, подпадающих под общее имя, чтобы алгоритму глубокого обучения было легко понять, что должен сказать пользователь. Каждому намерению дается определенное количество обучающих фраз, чтобы оно могло научиться классифицировать фразы в реальном времени.
Теперь, когда мы знаем, что такое классификация по намерениям, приступим к интересным вещам! Я написал блокнот, если вы хотите пройтись по мне, который вы можете найти в моем репозитории на Github здесь.
Для простоты воспользуемся следующей структурой каталогов:
Your directory ├───models ├───utils └───intent_classification.ipynb
Установка зависимостей
Установите необходимые зависимости, используя следующую команду:
pip install wget tensorflow==1.5 pandas numpy keras
Набор данных
Мы будем использовать общедоступный набор данных CLINC150. Это набор фраз для 150 различных целей в 10 доменах. Подробнее о наборе данных можно прочитать здесь.
Мы загрузим набор данных, используя:
import wget url = 'https://raw.githubusercontent.com/clinc/oos-eval/master/data/data_full.json' wget.download(url)
Подготовка набора данных
Набор данных уже разделен на наборы «поезд», «тест» и «проверка», но мы создадим свои собственные наборы для обучения и проверки, поскольку нам не нужен набор тестов. Мы сделаем это, объединив все наборы, а затем разделив их с помощью scikit-learn на наборы «train» и «validation». Это также создаст больше обучающих данных.
import numpy as np import json # Loading json data with open('data_full.json') as file: data = json.loads(file.read()) # Loading out-of-scope intent data val_oos = np.array(data['oos_val']) train_oos = np.array(data['oos_train']) test_oos = np.array(data['oos_test']) # Loading other intents data val_others = np.array(data['val']) train_others = np.array(data['train']) test_others = np.array(data['test']) # Merging out-of-scope and other intent data val = np.concatenate([val_oos,val_others]) train = np.concatenate([train_oos,train_others]) test = np.concatenate([test_oos,test_others]) data = np.concatenate([train,test,val]) data = data.T text = data[0] labels = data[1]
Затем мы создадим разделение поезда и проверки, используя:
from sklearn.model_selection import train_test_split train_txt,test_txt,train_label,test_labels = train_test_split(text,labels,test_size = 0.3)
Предварительная обработка набора данных
Поскольку глубокое обучение - это игра чисел, мы ожидаем, что наши данные будут в числовой форме, чтобы с ними можно было играть. Мы будем токенизировать наш набор данных; это означает разбивать предложения на отдельные части и преобразовывать этих лиц в числовые представления. Мы будем использовать K eras Tokenizer для токенизации наших фраз, используя следующий код:
from tensorflow.python.keras.preprocessing.text import Tokenizer from tensorflow.python.keras.preprocessing.sequence import pad_sequences max_num_words = 40000 classes = np.unique(labels) tokenizer = Tokenizer(num_words=max_num_words) tokenizer.fit_on_texts(train_txt) word_index = tokenizer.word_index
Чтобы передать наши данные в модель глубокого обучения, все наши фразы должны быть одинаковой длины. Мы будем дополнять все наши обучающие фразы 0, чтобы они стали одинаковой длины.
ls=[] for c in train_txt: ls.append(len(c.split())) maxLen=int(np.percentile(ls, 98)) train_sequences = tokenizer.texts_to_sequences(train_txt) train_sequences = pad_sequences(train_sequences, maxlen=maxLen, padding='post') test_sequences = tokenizer.texts_to_sequences(test_txt) test_sequences = pad_sequences(test_sequences, maxlen=maxLen, padding='post')
Затем нам нужно преобразовать наши метки в форму с горячим кодированием. Подробнее о горячем кодировании можно прочитать здесь.
from sklearn.preprocessing import OneHotEncoder,LabelEncoder label_encoder = LabelEncoder() integer_encoded = label_encoder.fit_transform(classes) onehot_encoder = OneHotEncoder(sparse=False) integer_encoded = integer_encoded.reshape(len(integer_encoded), 1) onehot_encoder.fit(integer_encoded) train_label_encoded = label_encoder.transform(train_label) train_label_encoded = train_label_encoded.reshape(len(train_label_encoded), 1) train_label = onehot_encoder.transform(train_label_encoded) test_labels_encoded = label_encoder.transform(test_labels) test_labels_encoded = test_labels_encoded.reshape(len(test_labels_encoded), 1) test_labels = onehot_encoder.transform(test_labels_encoded)
Прежде чем мы создадим нашу Модель ..
Прежде чем мы начнем обучение нашей модели, мы будем использовать Глобальные векторы. GloVe - это N-мерное векторное представление слов, обученное Стэнфордским университетом на большом корпусе. Поскольку он обучен на большом корпусе, это поможет модели еще лучше выучить фразы.
Мы загрузим GloVe, используя:
import wget url ='https://www.dropbox.com/s/a247ju2qsczh0be/glove.6B.100d.txt?dl=1' wget.download(url)
После завершения загрузки мы сохраним его в словаре Python:
embeddings_index={} with open('glove.6B.100d.txt', encoding='utf8') as f: for line in f: values = line.split() word = values[0] coefs = np.asarray(values[1:], dtype='float32') embeddings_index[word] = coefs
Поскольку GloVe содержит векторное представление всех слов из большого корпуса, нам понадобятся только те векторы слов, которые присутствуют в нашем корпусе. Мы создадим матрицу внедрения, которая будет содержать векторные представления только тех слов, которые присутствуют в нашем наборе данных. Поскольку наш набор данных уже был токенизирован, каждому токену в наборе данных присваивается уникальный номер токенизатором Keras. Этот уникальный номер можно рассматривать как индекс для вектора каждого слова в матрице вложения; это означает, что каждое n-е слово из токенизатора представлено вектором в n-й позиции в матрице внедрения.
all_embs = np.stack(embeddings_index.values()) emb_mean,emb_std = all_embs.mean(), all_embs.std() num_words = min(max_num_words, len(word_index))+1 embedding_dim=len(embeddings_index['the']) embedding_matrix = np.random.normal(emb_mean, emb_std, (num_words, embedding_dim)) for word, i in word_index.items(): if i >= max_num_words: break embedding_vector = embeddings_index.get(word) if embedding_vector is not None: embedding_matrix[i] = embedding_vector
Подготовка модели
Давайте представим архитектуру нашей модели, чтобы увидеть ее в действии.
from tensorflow.python.keras.models import Sequential from tensorflow.python.keras.layers import Dense, Input, Dropout, LSTM, Activation, Bidirectional,Embedding model = Sequential() model.add(Embedding(num_words, 100, trainable=False,input_length=train_sequences.shape[1], weights=[embedding_matrix])) model.add(Bidirectional(LSTM(256, return_sequences=True, recurrent_dropout=0.1, dropout=0.1), 'concat')) model.add(Dropout(0.3)) model.add(LSTM(256, return_sequences=False, recurrent_dropout=0.1, dropout=0.1)) model.add(Dropout(0.3)) model.add(Dense(50, activation='relu')) model.add(Dropout(0.3)) model.add(Dense(classes.shape[0], activation='softmax')) model.compile(loss='categorical_crossentropy', optimizer='adam', metrics=['acc'])
Мы передадим матрицу вложения в слой вложения как веса.
Модельное обучение
Наконец-то пришло время обучить модель.
history = model.fit(train_sequences, train_label, epochs = 20, batch_size = 64, shuffle=True, validation_data=[test_sequences, test_labels])
Это займет около часа, в зависимости от вашей машины. Когда обучение завершится, мы можем визуализировать показатели как:
import matplotlib.pyplot as plt %matplotlib inline plt.plot(history.history['acc']) plt.plot(history.history['val_acc']) plt.title('Model Accuracy') plt.ylabel('Accuracy') plt.xlabel('Epoch') plt.legend(['Train', 'Validation'], loc='upper left') plt.show()
Wohoo !! мы получаем точность обучения 92,45% и точность проверки 88,86%, что довольно прилично.
Вот кривая потерь:
import matplotlib.pyplot as plt %matplotlib inline plt.plot(history.history['loss']) plt.plot(history.history['val_loss']) plt.title('Model Loss') plt.ylabel('Loss') plt.xlabel('Epoch') plt.legend(['Train', 'Validation'], loc='upper left') plt.show()
Потеря обучения составляет около 0,2, а потеря проверки составляет около 0,5. Вы можете поиграть с архитектурой модели и посмотреть, уменьшатся ли потери даже 😉
Сохранение модели, токенизатора, кодировщика этикеток и этикеток
Давайте сохраним обученную модель, токенизатор, кодировщик меток и метки, чтобы использовать их в будущем.
import pickle import json model.save('models/intents.h5') with open('utils/classes.pkl','wb') as file: pickle.dump(classes,file) with open('utils/tokenizer.pkl','wb') as file: pickle.dump(tokenizer,file) with open('utils/label_encoder.pkl','wb') as file: pickle.dump(label_encoder,file)
Время увидеть все в действии
Мы прошли долгий путь ... давайте посмотрим, как выглядит конечный пункт назначения.
Я создал следующий класс, чтобы использовать нашу модель:
import numpy as np
from tensorflow.python.keras.preprocessing.sequence import pad_sequences
class IntentClassifier:
def __init__(self,classes,model,tokenizer,label_encoder):
self.classes = classes
self.classifier = model
self.tokenizer = tokenizer
self.label_encoder = label_encoder
def get_intent(self,text):
self.text = [text]
self.test_keras = self.tokenizer.texts_to_sequences(self.text)
self.test_keras_sequence = pad_sequences(self.test_keras, maxlen=16, padding='post')
self.pred = self.classifier.predict(self.test_keras_sequence)
return self.label_encoder.inverse_transform(np.argmax(self.pred,1))[0]
Чтобы использовать класс, мы сначала загрузим наши сохраненные файлы:
import pickle from tensorflow.python.keras.models import load_model model = load_model('models/intents.h5') with open('utils/classes.pkl','rb') as file: classes = pickle.load(file) with open('utils/tokenizer.pkl','rb') as file: tokenizer = pickle.load(file) with open('utils/label_encoder.pkl','rb') as file: label_encoder = pickle.load(file)
Время для теста! 😋
nlu = IntentClassifier(classes,model,tokenizer,label_encoder)
print(nlu.get_intent("is it cold in India right now"))
# Prints 'weather'
Вот и все, ребята! Спасибо за чтение😃. Удачного обучения!