Урок 4 охватывает следующее:

  • НЛП
  • Табличные данные
  • Совместная фильтрация

НЛП

Быстрый обзор

Обработка естественного языка — это текст и действия с ним. Классификация текстаимеетособенно полезные приложения, и именно на этом мы сосредоточимся.

Некоторые примеры:

  • Защита от спама
  • Выявление фейковых новостей
  • Установление диагноза по медицинским отчетам
  • Поиск упоминаний вашего продукта в Twitter

Создание классификатора текста можно разделить на три этапа:

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

Наш обучающий набор содержит 25 000 обзоров фильмов. В каждом обзоре есть некоторая информация о том, понравился он рецензенту или нет.

Помните, что наши нейронные сети — это всего лишь набор умножений матриц и нелинейностей (нормализация? замена отрицательных значений нулями). Матрицы весов, которые мы используем, начинаются со случайных чисел. Если вы начнете с случайныхпараметров и попытаетесь обучить эти параметры, чтобы научиться распознавать положительные и отрицательные отзывы о фильмах, у вас будет только 25 000 единиц или нулей. чтобы сообщить вам, был ли отзыв положительным или отрицательным. Этой информации явно недостаточно, учитывая, что этот классификатор должен достаточно хорошо знать английский язык, чтобы определить настроение отзыва. Обзоры также могут быть довольно тонкими, некоторые рецензенты используют сарказм. Обычно у вас недостаточно данных для решения такого рода проблем, поэтому вам следует использовать перенос обучение.

Трансферное обучение в НЛП

Ключевым моментом для начала является предварительно обученная модель, которая была обучена делать что-то отличное от того, что делаем мы.

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

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

Языковая модель – это модель, которая учится предсказывать следующее слово в предложении. Чтобы предсказать следующее слово в предложении, вам действительно нужно много знать язык и иметь много знаний о мире. В основном информация для определения различия между:

  • «Я хотел бы съесть горячее ___»: Очевидно, «собака», верно?
  • «Было жарко ___»: вероятно, «день».

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

Викитекст 103

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

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

Тонкая настройка Викитекста для создания новой языковой модели

Если мы сможем построить языковую модель, которая хорошо предсказывает следующее слово в рецензиях на фильмы, то эта модель сможет многое узнать о том, как именно пишутся рецензии на фильмы, или даже о названиях других фильмов.

Мы можем использовать большой набор обзоров фильмов, даже если мы не знаем их настроений, чтобы просто узнать, как пишутся обзоры фильмов. Таким образом, для всего предварительного обучения и тонкой настройки этой языковой модели нам не нужны метки. Янн ЛеКун называет это обучением под самоконтролем. Это классическая контролируемая модель — у нас есть ярлыки, но ярлыки — это не то, что кто-то создал. Метки встроены в сам набор данных.

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

Обзор основного процесса

  • Начните с данных в каком-то формате
  • Создайте TextDataBunch
  • Сохранить пакет данных
  • Нумерация (так называемая лексика)

Начните с некоторых данных в некотором формате. У нас есть файл CSV с данными IMDB. Прочтите это вместе с Pandas. Каждый обзор имеет некоторые данные, такие как: отрицательный или положительный, текст каждого обзора фильма и логическое значение того, находится ли он в проверочном наборе или в обучающем наборе.

path = untar_data(URLs.IMDB_SAMPLE)
path.ls()

[PosixPath('/home/jhoward/.fastai/data/imdb_sample/texts.csv'),
 PosixPath('/home/jhoward/.fastai/data/imdb_sample/models')]

df = pd.read_csv(path/'texts.csv')
df.head()

Таким образом, вы можете просто пойти TextDataBunch.from_csv, чтобы получить набор данных для языковой модели:

data_lm = TextDataBunch.from_csv(path, 'texts.csv')

И затем вы можете создать из него ученика обычным способом и подогнать его.

data_lm.save()

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

data = TextDataBunch.load(path)

Что произойдет за кулисами, если мы теперь загрузим его как набор данных классификации (это также позволит нам увидеть метки)?

data = TextClasDataBunch.load(path)
data.show_batch()

По сути, он создает отдельную единицу («токен») для каждой отдельной части слова. Большинство из них предназначены только для слов, но иногда, если это 's из it's, он получит свой собственный токен. Каждый бит пунктуации имеет тенденцию получать свой собственный токен (запятая, точка и т. д.).

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

data.vocab.itos[:10]
# output
['xxunk', 'xxpad', 'the', ',', '.', 'and', 'a', 'of', 'to', 'is']

Выходной список — это все возможные уникальные токены. Мы называем это лексикой или просто лексикой. Затем мы заменяем токены индексом (или идентификатором) того места, где этот токен находится в словаре. Это я назвал нумеризацией.

data.train_ds[0][0]
# output
Text xxbos xxfld 1 he now has a name , an identity , some memories..

data.train_ds[0][0].data[:10]

array([ 43,  44,  40,  34, 171,  62,   6, 352,   3,  47])

Каждое слово в словаре потребует отдельной строки в матрице весов в нашей нейронной сети. Чтобы весовая матрица не стала слишком большой, мы ограничиваем словарный запас до 60 000 слов. Кроме того, если слово встречается не более двух раз, мы исключаем его из словарного запаса. Такие слова, как xxunk, — это неизвестный токен, который не был достаточно распространенным, чтобы появляться в словаре.

  • xxfld: Это особая вещь, когда у вас есть заголовок, резюме, аннотация, тело (отдельные части документа), каждая из них получит отдельное поле и поэтому будет пронумерована (xxfld 2).
  • xxup: Если что-то написано заглавными буквами, оно будет переведено в нижний регистр, и к нему будет добавлен токен с именем xxup.

С API-интерфейсом блока данных

Вероятно, более простой способ сделать то же самое.

data = (TextList.from_csv(path, 'texts.csv', cols='text')
                .split_from_df(col=2)
                .label_from_df(cols=0)
                .databunch())

Другой подход к этому — просто решить:

  • Какой список вы создаете (какая у вас независимая переменная)? Итак, в данном случае моей независимой переменной является текст.
  • От чего это происходит? CSV.
  • Как вы хотите разделить его на проверку и обучение? Так что в этом случае столбец номер два был флагом is_valid.
  • Как вы хотите его обозначить? Например, с положительным или отрицательным настроением. Итак, в нулевом столбце было это.
  • Затем превратите это в набор данных.

Теперь давайте возьмем весь набор данных, который имеет:

  • 25 000 отзывов в обучении
  • 25 000 интервью на валидации
  • 50 000 рецензий на фильмы без присмотра (50 000 рецензий на фильмы, которые вообще не оценивались)

Языковая модель

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

Обучение Wikitext 103 занимает 2–3 дня на хорошем графическом процессоре. Итак, это задание начнется с модели, обученной на подмножестве.

Итак, мы начнем дорабатывать языковую модель нашей IMDB.

bs=48 # This is batch size, make lower if you run out of memory
data_lm = (TextList.from_folder(path)
           #Inputs: all the text files in path
            .filter_by_folder(include=['train', 'test']) 
           #We may have other temp folders that contain text files so we only keep what's in train and test
            .random_split_by_pct(0.1)
           #We randomly split and keep 10% (10,000 reviews) for validation
            .label_for_lm()           
           #We want to do a language model so we label accordingly
            .databunch(bs=bs))
data_lm.save('tmp_lm')

Примечания:

  • Это список текстовых файлов — полный IMDB на самом деле не в формате CSV. Каждый документ представляет собой отдельный текстовый файл.
  • Скажите, где это — в этом случае мы должны убедиться, что мы просто включили папки train и test.
  • Мы случайным образом разделили его на 0,1.

Почему мы разделяем его на 10% вместо того, чтобы использовать заранее определенный поезд и тест, который они нам дали? Причина, по которой мы можем это сделать, заключается в переносе обучения. Несмотря на то, что набор проверки должен быть отложен, на самом деле нужно оставить в стороне только метки.

Нам не разрешено использовать метки в тестовом наборе. Это имеет смысл, если вы думаете о соревновании Kaggle. Вы не можете использовать ярлыки, потому что они не дают их вам. Однако вы определенно можете использовать независимые переменные.

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

Поэтому, если вы, например, занимаетесь НЛП на Kaggle или у вас просто есть небольшое подмножество помеченных данных, убедитесь, что вы используете весь текст, который вам нужно обучить, в вашей языковой модели Потому что нет причин не делать этого.

  • Как мы будем его обозначать? Помните, что языковая модель имеет свои собственные ярлыки. Сам текст является меткой, поэтому метка для языковой модели(label_for_lm) делает это за нас.
  • И создайте группу данных и сохраните ее. Это занимает несколько минут для токенизации и нумерации.

Так как это занимает несколько минут, мы сохраняем его. Позже вы можете просто загрузить его. Нет необходимости запускать его снова.

data_lm = TextLMDataBunch.load(path, 'tmp_lm', bs=bs)
data_lm.show_batch() # Will show you some text output

Обучение

Теперь мы создаем обучаемый.

learn = language_model_learner(data_lm, pretrained_model=URLs.WT103, drop_mult=0.3)

Однако мы не создаем CNN обучение. Нам нужно создать языковую модель учащегося. Это не создает сверточную нейронную сеть, вместо этого создается RNN (рекуррентная нейронная сеть).

RNN имеет ту же базовую структуру, что и CNN. Входные данные входят в матрицу весов (умножение матрицы), затем вы заменяете минусы нулями, результат переходит в другую матрицу умножения и так далее несколько раз.

Как обычно, когда мы создаем ученика, вы должны передать две вещи:

  • Данные: вот данные нашей языковой модели.
  • Какую предторговую модель мы хотим использовать: здесь предторговая модель — это модель Wikitext 103, которая будет загружена для вас из fastai, если вы не использовали ее раньше, точно так же, как предварительно обученные модели ImageNet загружаются для вас.

drop_mult=0.3 устанавливает количество отсева, которое еще не обсуждалось. Мы кратко говорили об этой идее, что существует нечто, называемое регуляризацией, и вы можете уменьшить регуляризацию, чтобы избежать недостаточной подгонки. Так что пока просто знайте, что использование числа меньше единицы связано с тем, что, когда я впервые попытался запустить это, я был подходящим. Поэтому, если вы уменьшите это число, это позволит избежать недостаточной подгонки.

Хорошо, теперь у нас есть учащийся, так что мы можем lr_find, и он должен выглядеть довольно стандартно.

learn.lr_find()
learn.recorder.plot(skip_end=15)

Тогда мы можем уместить один цикл.

learn.fit_one_cycle(1, 1e-2, moms=(0.8,0.7))

вывод

Total time: 12:42
epoch  train_loss  valid_loss  accuracy
1      4.591534    4.429290    0.251909  (12:42)

Что здесь происходит, так это то, что мы просто настраиваем последние слои. Обычно после точной настройки последних слоев, следующее, что мы делаем, — это unfreeze и обучаем все это целиком. Итак, вот оно:

learn.unfreeze()
learn.fit_one_cycle(10, 1e-3, moms=(0.8,0.7))

вывод:

Total time: 2:22:17
epoch  train_loss  valid_loss  accuracy
1      4.307920    4.245430    0.271067  (14:14)
2      4.253745    4.162714    0.281017  (14:13)
3      4.166390    4.114120    0.287092  (14:14)
4      4.099329    4.068735    0.292060  (14:10)
5      4.048801    4.035339    0.295645  (14:12)
6      3.980410    4.009860    0.298551  (14:12)
7      3.947437    3.991286    0.300850  (14:14)
8      3.897383    3.977569    0.302463  (14:15)
9      3.866736    3.972447    0.303147  (14:14)
10     3.847952    3.972852    0.303105  (14:15)

Даже с довольно мощным графическим процессором это занимает два-три часа. Это, вероятно, можно было бы тренировать немного дольше.

Точность 0,3 означает, что мы правильно угадываем следующее слово в обзоре фильма примерно в трети случаев. Это звучит как довольно большое число — идея о том, что вы действительно можете так часто угадывать следующее слово. Так что это хороший признак того, что языковая модель работает достаточно хорошо. Для документов с более ограниченным доменом (таких как медицинские и юридические выписки) эта точность часто может быть намного выше. Так что иногда это может быть даже 50% и более. Но 0,3 или больше — это очень хорошо.

Прогнозирование с помощью языковой модели

Теперь вы можете запустить learn.predict и передать начало предложения, и он попытается закончить его за вас.

learn.predict('I liked this movie because ', 100, temperature=1.1, min_p=0.001)

вывод

Total time: 00:10
'I liked this movie because  of course after yeah funny later that the world reason settings - the movie that perfect the kill of the same plot - a mention of the most of course . do xxup diamonds and the " xxup disappeared kill of course and the movie niece , from the care more the story of the let character , " i was a lot \'s the little performance is not only . the excellent for the most of course , with the minutes night on the into movies ( ! , in the movie its the first ever ! \n\n a'

Он непредназначен для использования в качестве хорошей системы генерации текста. Это действительно больше предназначено для проверки того, что языковая модель создает что-то смутно осмысленное. Существует множество приемов, которые можно использовать для создания текста гораздо более высокого качества — ни один из них мы здесь не используем. Но вы можете видеть, что это определенно не случайные слова, которые он генерирует. Звучит как-то смутно по-английски, хотя в этом нет никакого смысла.

На данный момент у нас есть модель обзора фильма. Итак, сначала давайте сохраним его, чтобы мы могли загрузить его в наш классификатор (это будет предварительно обученная модель для классификатора). Но на самом деле вы не хотите сохранять все. Большая часть второй половины языковой модели связана с предсказанием следующего слова, а не с пониманием предложения на данный момент. Этот бит, который конкретно касается понимания предложения, называется кодировщик, так что это все, что нам нужно сохранить. Это бит, который понимает предложение, а не бит, который генерирует слово

learn.save_encoder('fine_tuned_enc')

Классификатор

Теперь мы наконец можем создать наш классификатор. Первым шагом, как обычно, является создание пакета данных.

data_clas = (TextList.from_folder(path, vocab=data_lm.vocab)
             .split_by_folder(valid='test')
             .label_from_folder(classes=['neg', 'pos'])
             .filter_missing_y()
             .databunch(bs=50))
data_clas.save('tmp_clas')
  • .from_folder(path, ...) — захватывает все текстовые файлы по пути
  • split_by_folder — обучающая и валидная папка (в которой хранятся только «поезд» и «тест», поэтому нет необходимости фильтровать)
  • label_from_folder(..) — удалить документы с метками, которых нет в списке выше (т. е. «unsup»)
  • filter_missing_y() — помечает их все своими папками?

Мы хотим убедиться, что здесь используется точно такой же словарный запас, который используется для языковой модели. Например, если слово под номером 10 было the в языковой модели, нам нужно убедиться, что слово под номером 10 имеет the в классификаторе. В противном случае предварительно обученная модель будет совершенно бесполезна. Вот почему мы передаем словарь из языковой модели, чтобы убедиться, что этот набор данных будет иметь точно такой же словарь из языковой модели. Это важный шаг.

split_by_folder — если вы помните, в прошлый раз мы разбивали случайным образом, но на этот раз нам нужно убедиться, что метки тестового набора не тронуты. Итак, разделяем по папкам.

На этот раз мы помечаем его не как языковая модель, а помечаем эти классы (['neg', 'pos']). Затем, наконец, создайте группу данных.

Обратите внимание на bs, или размер партии 50. Это было для графического процессора на 11 ГБ. Если у вас закончилась память из-за того, что у вашего оборудования меньше памяти, попробуйте уменьшить это число. Точно так же, если у вас есть машина с 16 ГБ, вы можете увеличить ее до 64.

Вот наш набор данных

data_clas = TextClasDataBunch.load(path, 'tmp_clas', bs=bs)
data_clas.show_batch()

learn = text_classifier_learner(data_clas, drop_mult=0.5)
learn.load_encoder('fine_tuned_enc')
learn.freeze()

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

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

learn.lr_find()
learn.recorder.plot()

learn.fit_one_cycle(1, 2e-2, moms=(0.8,0.7))

вывод:

Total time: 02:46
epoch  train_loss  valid_loss  accuracy
1      0.294225    0.210385    0.918960  (02:46)

У нас уже почти 92% точности менее чем за три минуты тренировки! В вашей конкретной области (юриспруденция, медицина, журналистика, правительство и т. д.) вам, вероятно, потребуется обучить языковую модель вашей области только один раз. Это может занять ночь, чтобы хорошо тренироваться. Но как только вы это сделаете, вы сможете очень быстро создавать с его помощью всевозможные классификаторы и модели. В данном случае довольно неплохая модель через три минуты. Поэтому, когда вы впервые начнете это делать, вас может раздражать, что ваши первые модели занимают четыре часа или больше, чтобы создать эту языковую модель. Но важно помнить, что вам нужно сделать это только один раз для всей интересующей вас области знаний. Затем вы можете создать множество различных классификаторов и других моделей поверх этого за считанные минуты.

learn.save('first')

Мы можем сохранить это, чтобы нам не пришлось запускать его снова.

learn.load('first')
learn.freeze_to(-2)
learn.fit_one_cycle(1, slice(1e-2/(2.6**4),1e-2), moms=(0.8,0.7))

вывод:

Total time: 03:03
epoch  train_loss  valid_loss  accuracy
1      0.268781    0.180993    0.930760  (03:03)

Обычно мы бы unfreeze. Вместо этого используйте freeze_to(-2). Это размораживает последние два слоя, а не размораживает все целиком. Это характерно для классификации текста, так как размораживание одного слоя за раз более полезно, чем размораживание всего сразу.

  1. разморозить последние два слоя
  2. тренируйся еще немного
  3. снова разморозить следующий слой
  4. тренируйся еще немного
  5. разморозить все это
  6. тренируйся еще немного
learn.save('second')

learn.load('second')
learn.freeze_to(-3)
learn.fit_one_cycle(1, slice(5e-3/(2.6**4),5e-3), moms=(0.8,0.7))

вывод:

Total time: 04:06
epoch  train_loss  valid_loss  accuracy
1      0.211133    0.161494    0.941280  (04:06)

learn.save('third')
learn.load('third')
learn.unfreeze()
learn.fit_one_cycle(2, slice(1e-3/(2.6**4),1e-3), moms=(0.8,0.7))

Примечание: moms=(0.8,0.7) — импульс равен 0,8,0,7. В основном, фастай найден для тренировки RNN, он действительно помогает немного уменьшить импульс.

Теперь у нас точность 94% примерно через полчаса тренировки. На классификаторе проводится значительно меньше обучения. Мы можем улучшить это с помощью нескольких приемов, которые могут быть обсуждены позже в курсе.

Больше подробных заметок по всему уроку.