import numpy as np
import pandas as pd
# from fastai.text import *
import html
from fastai.text.all import *
from fastbook import *
from IPython.display import display,HTML

Обмен данными с помощью API среднего уровня fastai

Мы увидели, что Tokenizerи что Numericalize делают с коллекцией текстов, и как они используются в API блока данных, который обрабатывает эти преобразования за нас напрямую, используя TextBlock. Но что, если мы хотим применить только одно из этих преобразований, чтобы увидеть промежуточные результаты или потому, что мы уже токенизировали тексты? В более общем плане, что мы можем сделать, если API блока данных недостаточно гибок, чтобы приспособиться к нашему конкретному варианту использования? Для этого нам нужно использовать API среднего уровня fastai для обработки данных. API блока данных построен поверх этого уровня, поэтому он позволит вам делать все, что делает API блока данных, и многое другое.

def readText(fileName): # Объявляем функции для чтения файла. На вход отправляем путь к файлу
  f = open(fileName, 'r')        # Задаем открытие нужного файла в режиме чтения
  text = f.read()                # Читаем текст
  text = text.replace("\n", " ") # Переносы строки переводим в пробелы
  
  return text                    # Возвращаем текст файла

className = ["О. Генри", "Стругацкие", "Булгаков", "Саймак", "Фрай", "Брэдберри"] # Объявляем интересующие нас классы
nClasses = len(className) # Считаем количество классов
#Загружаем обучающие тексты

trainText = [] #Формируем обучающие тексты
testText = [] #Формируем тестовые тексты

#Формирование необходимо произвести следующим образом 
#Класс каждого i-ого эллемента в обучающей выборке должен соответствовать 
#классу каждого i-ого эллемента в тестовой выборке

for i in className: #Проходим по каждому классу
  for j in os.listdir('../../dataset/textNew/'): #Проходим по каждому файлу в папке с текстами #
    if i in j: #Проверяем, содержит ли файл j в названии имя класса i
      
      if 'Обучающая' in j: #Если в имени найденного класса есть строка "Обучающая" 
        trainText.append(readText('../../dataset/textNew/' + j)) #добавляем в обучающую выборку
        print(j, 'добавлен в обучающую выборку') #Выводим информацию
      if 'Тестовая' in j: #Если в имени найденного класса есть строка "Тестовая"
        testText.append(readText('../../dataset/textNew/' + j)) #добавляем в обучающую выборку
        print(j, 'добавлен в тестовую выборку') #Выводим информацию
  print()

Углубляемся в многоуровневый API Fastai

примечание: API среднего уровня: API среднего уровня не только содержит функции для создания DataLoaders. Он также имеет систему обратного вызова , которая позволяет нам настраивать цикл обучения так, как нам нравится, и общий оптимизатор

tok = Tokenizer.from_folder(path)
tok.setup(txts)
toks = txts.map(tok)
toks[1]
len(toks)
num = Numericalize()
num.setup(toks)
nums = toks.map(num)
nums[0][:10]

У классов также есть decode метод. Например, Numericalize.decodeвозвращает нам токены строки:

nums_dec = num.decode(nums[0][:10]); nums_dec
tok.decode(nums_dec)
tok((txts[0], txts[1], txts[2], txts[3]))

decode используется fastai show_batch и show_results, а также некоторыми другими методами вывода, для преобразования прогнозов и мини-пакетов в понятное для человека представление.

Для каждого из tokили numв предыдущем примере мы создали объект, названный setup методом (который при необходимости обучает токенизатор tok и создает словарь для него num), применили его к нашим необработанным текстам (вызывая объект как функцию), а затем наконец декодировал результат до понятного представления. Эти шаги необходимы для большинства задач предварительной обработки данных, поэтому fastai предоставляет класс, который их инкапсулирует. Это Transform класс. Как Tokenize и Numericalize в Transforms.

Написание собственного преобразования

Если вам нужно либо, setup либо decode, вам нужно будет создать подкласс Transform для реализации фактического поведения кодирования в encodes, затем (необязательно), поведение настройки setups и поведение декодирования в decodes:

@Transform
def f(x:int): return x+1
f(2),f(2.0)
class NormalizeMean(Transform):
    def setups(self, items): self.mean = sum(items)/len(items)
    def encodes(self, x): return x-self.mean
    def decodes(self, x): return x+self.mean

Обратите внимание, что вызываемый метод и реализованный метод различаются для каждого из этих методов:

|====== | Class | To call | To implement | `nn.Module` (PyTorch) | `()` (i.e., call as function) | `forward` | `Transform` | `()` | `encodes` | `Transform` | `decode()` | `decodes` | `Transform` | `setup()` | `setups` |====== Так, например, вы никогда не позвоните setupsнапрямую, а вместо этого вызывает setup. Причина этого в том, что он setup выполняет некоторую работу до и после вызова setups для вас. Чтобы узнать больше о Transforms и о том, как вы можете использовать их для реализации различного поведения в зависимости от типа ввода, обязательно ознакомьтесь с руководствами в документации fastai.

tfm = NormalizeMean()
tfm.setup([1,2,3,4,5])
start = 2
y = tfm(start)
z = tfm.decode(y)
tfm.mean,y,z

Чтобы скомпоновать несколько преобразований вместе, fastaiпредоставляет Pipeline класс. Мы определяем a Pipeline, передавая ему список Transforms; затем он будет составлять преобразования внутри него. Когда вы вызываете Pipelineобъект, он автоматически вызывает преобразования внутри в следующем порядке:

tfms = Pipeline([tok, num])
t = tfms(txts[0]); t[:20]
tfms.decode(t)[:100]

TfmdLists и наборы данных: преобразованные коллекции

Ваши данные обычно представляют собой набор необработанных элементов (например, имен файлов или строк в DataFrame), к которым вы хотите применить последовательность преобразований. Мы только что видели, что последовательность преобразований представлена Pipelineв фастае. Класс, который группирует это вместе Pipelineс вашими необработанными элементами, называется TfmdLists.

tls = TfmdLists(files, [Tokenizer.from_folder(path), Numericalize])

При инициализации TfmdLists будет автоматически вызывать setup метод каждого из них Transform по порядку, предоставляя им не сырые элементы, а элементы, преобразованные всеми предыдущими Transform по порядку. Мы можем получить результат для Pipeline любого необработанного элемента, просто проиндексировав его в TfmdLists:

t = tls[0]; t[:20]
tls.decode(t)[:100]
tls.show(t)

Объект TfmdLists назван с буквой «s», потому что он может обрабатывать набор для обучения и проверки с splitsаргументом. Вам просто нужно передать индексы того, какие элементы находятся в обучающем наборе, а какие - в проверочном:

cut = int(len(files)*0.8)
splits = [list(range(cut)), list(range(cut,len(files)))]
tls = TfmdLists(files, [Tokenizer.from_folder(path), Numericalize], 
                splits=splits)
tls.valid[0][:20]

Если вы вручную написали класс, Transform который выполняет всю вашу предварительную обработку одновременно, превращая необработанные элементы в кортеж с входными данными и целями, то TfmdLists это класс, который вам нужен. Вы можете напрямую преобразовать его в DataLoader sобъект с помощью dataloaders метода. Это то, что мы сделаем в нашем примере с Siamese tutorial позже в этой главе.

Однако в целом у вас будет два (или более) параллельных конвейера преобразований: один для обработки исходных элементов во входные данные, а другой - для обработки исходных элементов в целевые объекты. Например, здесь конвейер, который мы определили, обрабатывает только необработанный текст во входные данные. Если мы хотим провести классификацию текста, мы также должны преобразовать метки в цели.

Для этого нам нужно сделать две вещи. Сначала мы берем имя метки из родительской папки. Для этого есть функция parent_label,:

lbls = files.map(parent_label)
lbls

Затем нам понадобится программа, Transform которая будет захватывать уникальные элементы и строить с ними словарь во время настройки, а затем при вызове преобразовывать строковые метки в целые числа. Fastai предоставляет это для нас; это называется Categorize:

cat = Categorize()
cat.setup(lbls)
cat.vocab, cat(lbls[0])

Чтобы выполнить всю настройку автоматически в нашем списке файлов, мы можем создать, TfmdLists как и раньше:

tls_y = TfmdLists(files, [parent_label, Categorize()])
tls_y[0]

Datasets

Datasets применяет два (или более) конвейера параллельно к одному и тому же необработанному объекту и строит кортеж с результатом. Например TfmdLists, он автоматически выполнит настройку для нас, и когда мы проиндексируем в a Datasets, он вернет нам кортеж с результатами каждого конвейера:

x_tfms = [Tokenizer.from_folder(path), Numericalize]
y_tfms = [parent_label, Categorize()]
dsets = Datasets(files, [x_tfms, y_tfms])
x,y = dsets[0]
x[:20],y

Подобно a TfmdLists, мы можем перейти splits к a, Datasets чтобы разделить наши данные между обучающими и проверочными наборами:

x_tfms = [Tokenizer.from_folder(path), Numericalize]
y_tfms = [parent_label, Categorize()]
dsets = Datasets(files, [x_tfms, y_tfms], splits=splits)
x,y = dsets.valid[0]
x[:20],y
t = dsets.valid[0]
dsets.decode(t)

Последний шаг - преобразовать наш Datasets объект вDataLoaders, что можно сделать с помощью dataloaders метода. Здесь нам нужно передать специальный аргумент, чтобы решить проблему заполнения (как мы видели в предыдущей главе). Это должно произойти непосредственно перед тем, как мы объединим элементы, поэтому мы передаем это before_batch:

dls = dsets.dataloaders(bs=64, before_batch=pad_input)

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

  • after_item:: Применяется к каждому элементу после захвата его в наборе данных. Это эквивалент item_tfms в DataBlock.
  • before_batch:: Применяется к списку элементов до их сопоставления. Это идеальное место для размещения предметов одинакового размера.
  • after_batch:: Наносится на партию в целом после ее изготовления. Это эквивалент batch_tfms в DataBlock.

В заключение, вот полный код, необходимый для подготовки данных к классификации текста:

tfms = [[Tokenizer.from_folder(path), Numericalize], [parent_label, Categorize]]
files = get_text_files(path, folders = ['train', 'test'])
splits = GrandparentSplitter(valid_name='test')(files)
dsets = Datasets(files, tfms, splits=splits)
dls = dsets.dataloaders(dl_type=SortedDL, before_batch=pad_input)

Два отличия от предыдущего кода - это использование GrandparentSplitter для разделения данных обучения и проверки и dl_type аргумент. Это означает, что dataloaders нужно использовать SortedDL класс DataLoader, а не обычный. SortedDL создает партии, помещая в партии образцы примерно одинаковой длины.

Это делает то же самое, что и предыдущее DataBlock:

path = untar_data(URLs.IMDB)
dls = DataBlock(
    blocks=(TextBlock.from_folder(path),CategoryBlock),
    get_y = parent_label,
    get_items=partial(get_text_files, folders=['train', 'test']),
    splitter=GrandparentSplitter(valid_name='test')
).dataloaders(path)

Применение API данных среднего уровня: SiamesePair

Сиамская модель берет два изображений и должна определить , если они тот же класс или нет. В этом примере мы снова будем использовать набор данных Pet и подготовить данные для модели, которая должна будет предсказать, принадлежат ли два изображения домашних животных к одной и той же породе или нет. Мы объясним здесь, как подготовить данные для такой модели, а затем обучим эту модель в < >. Перво-наперво, давайте получим изображения в нашем наборе данных:

from fastai.vision.all import *
path = untar_data(URLs.PETS)
files = get_image_files(path/"images")

Если бы мы вообще не заботились о отображении наших объектов, мы могли бы напрямую создать одно преобразование для полной предварительной обработки этого списка файлов. Однако мы захотим взглянуть на эти изображения, поэтому нам нужно создать собственный тип. Когда вы вызываете showметод для объекта TfmdLists или Datasets объекта, он будет декодировать элементы, пока не достигнет типа, содержащего show метод, и будет использовать его для отображения объекта. Этому show методу передается a ctx, который может быть matplotlib осью для изображений или строкой DataFrame для текстов.

Здесь мы создаем SiameseImage объект, который является подклассом fastupleи предназначен для хранения трех вещей: двух изображений и логического значения, True если изображения принадлежат к одному и тому же виду. Мы также реализуем специальный show метод, который объединяет два изображения с черной линией посередине. Не беспокойтесь слишком о части, которая находится в ifтесте (которая должна показать, SiameseImage когда изображения являются изображениями Python, а не тензорами); важная часть находится в последних трех строках:

class SiameseImage(fastuple):
    def show(self, ctx=None, **kwargs): 
        img1,img2,same_breed = self
        if not isinstance(img1, Tensor):
            if img2.size != img1.size: img2 = img2.resize(img1.size)
            t1,t2 = tensor(img1),tensor(img2)
            t1,t2 = t1.permute(2,0,1),t2.permute(2,0,1)
        else: t1,t2 = img1,img2
        line = t1.new_zeros(t1.shape[0], t1.shape[1], 10)
        return show_image(torch.cat([t1,line,t2], dim=2), 
                          title=same_breed, ctx=ctx)

Давайте сначала создадим SiameseImageи проверим, как showработает наш метод:

img = PILImage.create(files[0])
s = SiameseImage(img, img, True)
s.show();

png

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

img1 = PILImage.create(files[1])
s1 = SiameseImage(img, img1, False)
s1.show();

png

Важная вещь с преобразованиями, которую мы видели ранее, заключается в том, что они отправляют кортежи или их подклассы. Именно поэтому мы решили создать подкласс fastuple в этом случае - таким образом мы можем применить любое преобразование, которое работает с изображениями, к нашим, SiameseImage и оно будет применяться к каждому изображению в кортеже:

s2 = Resize(224)(s1)
s2.show();

png

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

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

def label_func(fname):
    return re.match(r'^(.*)_\d+.jpg$', fname.name).groups()[0]

Для каждого изображения наша трансформация с вероятностью 0,5 будет рисовать изображение из того же класса и возвращать a SiameseImage с истинной меткой или рисовать изображение из другого класса и возвращать a SiameseImage с ложной меткой. Все это делается в частной _drawфункции. Есть одно различие между обучающим и проверочным наборами, поэтому преобразование необходимо инициализировать с помощью разбиений: в обучающем наборе мы будем делать этот случайный выбор каждый раз, когда мы читаем изображение, тогда как на проверочном наборе мы делаем это случайное выбрать раз и навсегда при инициализации. Таким образом, мы получаем более разнообразные образцы во время обучения, но всегда один и тот же набор для проверки:

class SiameseTransform(Transform):
    def __init__(self, files, label_func, splits):
        self.labels = files.map(label_func).unique()
        self.lbl2files = {l: L(f for f in files if label_func(f) == l) 
                          for l in self.labels}
        self.label_func = label_func
        self.valid = {f: self._draw(f) for f in files[splits[1]]}
        
    def encodes(self, f):
        f2,t = self.valid.get(f, self._draw(f))
        img1,img2 = PILImage.create(f),PILImage.create(f2)
        return SiameseImage(img1, img2, t)
    
    def _draw(self, f):
        same = random.random() < 0.5
        cls = self.label_func(f)
        if not same: 
            cls = random.choice(L(l for l in self.labels if l != cls)) 
        return random.choice(self.lbl2files[cls]),same

Затем мы можем создать наше основное преобразование:

splits = RandomSplitter()(files)
tfm = SiameseTransform(files, label_func, splits)
tfm(files[0]).show();

png

В API среднего уровня для сбора данных у нас есть два объекта, которые могут помочь нам применять преобразования к набору элементов, TfmdLists и Datasets. Если вы помните, что мы только что видели, один применяет несколько Pipeline преобразований, а другой применяет несколько Pipeline преобразований параллельно, чтобы построить кортежи. Здесь наше основное преобразование уже создает кортежи, поэтому мы используем TfmdLists:

tls = TfmdLists(files, tfm, splits=splits)
show_at(tls.valid, 0);

png

И, наконец, мы можем получить наши данные DataLoaders, вызвав dataloaders метод. Здесь следует остерегаться того, что этот метод не принимает item_tfms и batch_tfms не любит DataBlock. У фастая DataLoader есть несколько крючков, названных в честь событий; здесь то, что мы применяем к элементам после того, как они были захвачены, называется after_item, и то, что мы применяем к пакету после его создания, называется after_batch:

dls = tls.dataloaders(after_item=[Resize(224), ToTensor], 
    after_batch=[IntToFloatTensor, Normalize.from_stats(*imagenet_stats)])

Обратите внимание, что нам нужно передать больше преобразований, чем обычно, потому что API блока данных обычно добавляет их автоматически:

  • ToTensor это тот, который преобразует изображения в тензоры (опять же, он применяется к каждой части кортежа).
  • IntToFloatTensor преобразует тензор изображений, содержащих целые числа от 0 до 255, в тензор чисел с плавающей запятой и делит на 255, чтобы получить значения от 0 до 1.

Теперь мы можем обучить модель, используя это DataLoaders. Потребуется немного больше настроек, чем у обычной модели, предоставляемой, cnn_learnerпоскольку она должна принимать два изображения вместо одного, но мы увидим, как создать такую ​​модель и обучить ее в < >.

Вывод

fastai предоставляет многоуровневый API. Чтобы получить данные, когда они находятся в одной из обычных настроек, требуется одна строка кода, что позволяет новичкам сосредоточиться на обучении модели, не тратя слишком много времени на сборку данных. Затем высокоуровневый API блоков данных дает вам большую гибкость, позволяя смешивать и сопоставлять некоторые строительные блоки. Под ним API среднего уровня дает вам большую гибкость для применения любых преобразований к вашим элементам. В ваших реальных проблемах это, вероятно, то, что вам нужно будет использовать, и мы надеемся, что это сделает этап обработки данных максимально простым.

Опросник

  1. Почему мы говорим, что у fastai «многоуровневый» API? Что это значит?
  2. Почему Transformесть decode метод? Что оно делает?
  3. Почему Transform есть setup метод? Что оно делает?
  4. Как Transform работает при вызове кортежа?
  5. Какие методы вам нужно реализовать при написании собственного Transform?
  6. Напишите Normalize преобразование, которое полностью нормализует элементы (вычтите среднее значение и разделите на стандартное отклонение набора данных), и которое может декодировать это поведение. Старайтесь не подглядывать!
  7. Напишите a, Transform который выполняет числовое преобразование токенизированных текстов (он должен автоматически устанавливать свой словарь из видимого набора данных и иметь decode метод). Если нужна помощь, посмотрите исходный код fastai.
  8. Что такое Pipeline?
  9. Что такое TfmdLists?
  10. Что такое Datasets? Чем он отличается от TfmdLists?
  11. Почему TfmdLists и Datasets названы с буквой «s»?
  12. Как можно построить a DataLoaders из a TfmdListsили a Datasets?
  13. Как вы проходите item_tfms и batch_tfmsкогда строите DataLoaders a TfmdLists или a Datasets?
  14. Что вам нужно сделать, если вы хотите, чтобы ваши пользовательские элементы работали с такими методами, как show_batch или show_results?
  15. Почему мы можем легко применить преобразование увеличения данных fastai к SiamesePair созданному нами?

Дальнейшие исследования

  1. Используйте API среднего уровня для подготовки данных в DataLoaders ваших собственных наборах данных. Попробуйте это с наборами данных Pet и наборами данных для взрослых из главы 1.
  2. Посмотрите руководство по Siamese документация в документации fastai, чтобы узнать, как настроить поведение show_batch и show_results для нового типа предметов. Реализуйте это в собственном проекте.

Понимание приложений fastai: подведение итогов

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

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

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

Раскрыть комментарии 1

Иван
этот курс- первое введение в код НЛП, обучающая философия Fast.ai дает студентам ощущение «всей игры», прежде чем углубляться в детали более низкого уровня

Чтобы оставить комментарий , Вам необходимо Авторизоваться или пройти Регистрацию