Как сохранить набор данных torchtext?
Я работаю с текстом и пользуюсь torchtext.data.Dataset
, Создание набора данных занимает значительное время. Для просто запуска программы это все еще приемлемо. Но я хотел бы отладить код факела для нейронной сети. И если python запускается в режиме отладки, создание набора данных занимает примерно 20 минут (!!). Это просто для того, чтобы получить рабочую среду, в которой я могу отлаживать пошаговый код нейронной сети.
Я хотел бы сохранить набор данных, например, с рассолом. Этот пример кода взят отсюда, но я удалил все, что не нужно для этого примера:
from torchtext import data
from fastai.nlp import *
PATH = 'data/aclImdb/'
TRN_PATH = 'train/all/'
VAL_PATH = 'test/all/'
TRN = f'{PATH}{TRN_PATH}'
VAL = f'{PATH}{VAL_PATH}'
TEXT = data.Field(lower=True, tokenize="spacy")
bs = 64;
bptt = 70
FILES = dict(train=TRN_PATH, validation=VAL_PATH, test=VAL_PATH)
md = LanguageModelData.from_text_files(PATH, TEXT, **FILES, bs=bs, bptt=bptt, min_freq=10)
with open("md.pkl", "wb") as file:
pickle.dump(md, file)
Для запуска кода вам нужен набор данных aclImdb, его можно скачать отсюда. Извлеките его в data/
Папка рядом с этим фрагментом кода. Код выдает ошибку в последней строке, где используется pickle:
Traceback (most recent call last):
File "/home/lhk/programming/fastai_sandbox/lesson4-imdb2.py", line 27, in <module>
pickle.dump(md, file)
TypeError: 'generator' object is not callable
Образцы из Фастая часто используют укроп вместо маринада. Но это тоже не работает для меня.
0 ответов
Придумал себе следующие функции:
import dill
from pathlib import Path
import torch
from torchtext.data import Dataset
def save_dataset(dataset, path):
if not isinstance(path, Path):
path = Path(path)
path.mkdir(parents=True, exist_ok=True)
torch.save(dataset.examples, path/"examples.pkl", pickle_module=dill)
torch.save(dataset.fields, path/"fields.pkl", pickle_module=dill)
def load_dataset(path):
if not isinstance(path, Path):
path = Path(path)
examples = torch.load(path/"examples.pkl", pickle_module=dill)
fields = torch.load(path/"fields.pkl", pickle_module=dill)
return Dataset(examples, fields)
Не то чтобы реальные объекты могли немного отличаться, например, если вы сохраните TabularDataset
, тогда load_dataset
возвращает экземпляр класса Dataset
. Это вряд ли повлияет на конвейер данных, но может потребовать дополнительных усилий для тестирования. В случае настраиваемого токенизатора он также должен быть сериализуемым (например, без лямбда-функций и т. Д.).
Вы можете использовать укроп вместо маринада. Меня устраивает. Вы можете сохранить поле текста факела, как
TEXT = data.Field(sequential=True, tokenize=tokenizer, lower=True,fix_length=200,batch_first=True)
with open("model/TEXT.Field","wb")as f:
dill.dump(TEXT,f)
И загрузить поле как
with open("model/TEXT.Field","rb")as f:
TEXT=dill.load(f)
Официальная поддержка кода находится в стадии разработки , вы можете следовать https://github.com/pytorch/text/issues/451 и https://github.com/pytorch/text/issues/73.
Вы всегда можете использовать рассол для сброса объектов, но имейте в виду одну вещь, что выгрузка списка объектов словаря или полей не выполняется модулем, поэтому, в первую очередь, постарайтесь сначала разложить список
Сохранение объекта DataSet в файле pickle для последующей легкой загрузки
def save_to_pickle(dataSetObject,PATH):
with open(PATH,'wb') as output:
for i in dataSetObject:
pickle.dump(vars(i), output, pickle.HIGHEST_PROTOCOL)
Самое сложное еще впереди, да, загрузка файла pickle....;)
Сначала попробуйте найти все имена полей и атрибуты полей, а затем перейдите к уничтожению
Чтобы загрузить файл рассола в DataSetObject
def load_pickle(PATH, FIELDNAMES, FIELD):
dataList = []
with open(PATH, "rb") as input_file:
while True:
try:
# Taking the dictionary instance as the input Instance
inputInstance = pickle.load(input_file)
# plugging it into the list
dataInstance = [inputInstance[FIELDNAMES[0]],inputInstance[FIELDNAMES[1]]]
# Finally creating an example objects list
dataList.append(Example().fromlist(dataInstance,fields=FIELD))
except EOFError:
break
# At last creating a data Set Object
exampleListObject = Dataset(dataList, fields=data_fields)
return exampleListObject
Это хакерское решение сработало в моем случае, надеюсь, вы найдете его полезным и в вашем случае.
Кстати любые предложения приветствуются:).
Подход с рассолом и укропом подойдет, если ваш набор данных небольшой. Но если вы работаете с большими наборами данных, я не рекомендую это, так как это будет слишком медленно.
Я просто сохраняю примеры (итеративно) в виде JSON-строк. Причина этого в том, что сохранение всего объекта Dataset занимает много времени, плюс вам нужны уловки сериализации, такие как укроп, которые делают сериализацию еще медленнее.
Более того, эти сериализаторы занимают много памяти (некоторые из них даже создают копии набора данных), и если они начнут использовать память подкачки, все готово. Этот процесс займет так много времени, что вы, вероятно, завершите его до того, как он завершится.
Поэтому я прихожу к следующему подходу:
- Перебираем примеры
- Преобразование каждого примера (на лету) в строку JSON
- Запишите эту JSON-строку в текстовый файл (по одному образцу на строку)
- При загрузке добавьте примеры в объект Dataset вместе с полями
def save_examples(dataset, savepath):
with open(savepath, 'w') as f:
# Save num. elements (not really need it)
f.write(json.dumps(total)) # Write examples length
f.write("\n")
# Save elements
for pair in dataset.examples:
data = [pair.src, pair.trg]
f.write(json.dumps(data)) # Write samples
f.write("\n")
def load_examples(filename):
examples = []
with open(filename, 'r') as f:
# Read num. elements (not really need it)
total = json.loads(f.readline())
# Save elements
for i in range(total):
line = f.readline()
example = json.loads(line)
# example = data.Example().fromlist(example, fields) # Create Example obj. (you can do it here or later)
examples.append(example)
end = time.time()
print(end - start)
return examples
Затем вы можете просто перестроить набор данных:
# Define fields
SRC = data.Field(...)
TRG = data.Field(...)
fields = [('src', SRC), ('trg', TRG)]
# Load examples from JSON and convert them to "Example objects"
examples = load_examples(filename)
examples = [data.Example().fromlist(d, fields) for d in examples]
# Build dataset
mydataset = Dataset(examples, fields)
Причина, по которой я использую JSON вместо pickle, dill, msgpack и т. Д., Не произвольна.
Я провел несколько тестов, и вот результаты:
Dataset size: 2x (1,960,641)
Saving times:
- Pickle/Dill*: >30-45 min (...or froze my computer)
- MessagePack (iterative): 123.44 sec
100%|██████████| 1960641/1960641 [02:03<00:00, 15906.52it/s]
- JSON (iterative): 16.33 sec
100%|██████████| 1960641/1960641 [00:15<00:00, 125955.90it/s]
- JSON (bulk): 46.54 sec (memory problems)
Loading times:
- Pickle/Dill*: -
- MessagePack (iterative): 143.79 sec
100%|██████████| 1960641/1960641 [02:23<00:00, 13635.20it/s]
- JSON (iterative): 33.83 sec
100%|██████████| 1960641/1960641 [00:33<00:00, 57956.28it/s]
- JSON (bulk): 27.43 sec
* Такой же подход, как и другие ответы