Помогите мировому сообществу лучше понять болезнь, проведя огромное количество исследований.
Проект
В ответ на пандемию COVID-19 Белый дом и коалиция ведущих исследовательских групп подготовили набор данных открытых исследований COVID-19.
Это сборник большого количества исследовательских работ о COVID-19. Мы надеемся, что сообщество машинного обучения поможет извлечь полезные идеи из этой большой коллекции исследовательских работ.
Данные и подробное описание можно найти здесь на Kaggle.
Что такое идея?
Идея состоит в том, чтобы создать языковую модель, обученную на «основном тексте» исследовательской работы, доступной в этом наборе данных. Полученную языковую модель можно затем использовать для создания своего рода классификатора.
В настоящее время у нас нет помеченного набора данных о COVID-19, но я надеюсь, что в случае доступности помеченного набора данных мы сможем использовать эту языковую модель для выполнения какой-либо задачи классификации.
Даже если помеченный набор данных недоступен, мы все равно можем настроить эту языковую модель (или ее улучшенную версию), чтобы предсказать следующую последовательность слов для поискового запроса и получить скрытую информацию, которая в противном случае была бы скрыта от человеческий глаз.
Импорт необходимых библиотек
Я использую fastai для создания языковой модели. Итак, импортируется fast.text. Json используется для анализа данных json.
from fastai.text import * import numpy as np import json
Давайте установим все пути?
Давайте установим весь путь, необходимый для работы с нашими данными.
path
— это корневой входной каталог.outputPath
— это то место, где мы будем сохранять наш csv, модели и т. д.datasetPath
— это каталог для набора данных.
path = Path('/kaggle/input') outputPath = Path('/kaggle/working') datasetPath = path/'CORD-19-research-challenge/'
Давайте также установим путь к коллекции научных статей. Для этой языковой модели мы выберем подмножество всего корпуса. Это поможет нам в быстром первоначальном тестировании нашего кода.
pmcCustomLicense
— это путь к каталогу, содержащему файлы json для исследовательских работ, полученных из pmc.biorxivMedrxiv
— это путь к каталогу, содержащему json-файлы исследовательских работ, взятых из biorxiv-Medrxiv.
pmcCustomLicense = datasetPath/'custom_license/custom_license' biorxivMedrxiv = datasetPath/'biorxiv_medrxiv/biorxiv_medrxiv'
Как разобрать данные из Json?
Данные из исследовательских работ хранятся в этом наборе данных в формате json. Нам нужно проанализировать данные из файлов json, прежде чем использовать это.
Для этого проекта я пытаюсь построить языковую модель, и в настоящее время меня интересует ключ «body_text» файлов json.
Это где корпус научных работ присутствует.
Здесь я использовал функции и приемы, уже использованные в этом ноутбуке. Я благодарен создателю этого блокнота за это.
Я внес несколько изменений в функции оригинальной записной книжки, но почти все они остались такими же, как исходное содержимое.
Здесь идея состоит в том, чтобы проанализировать файлы json для извлечения основного текста в кадр данных pandas.
Функции делают следующее →
load_files()
-
- Принимает кортеж в качестве аргумента. Этот кортеж представляет собой набор списков, имеющих путь к каталогам, из которых необходимо получить json.
- Затем эта функция проходит через каждый из этих каталогов и добавляет их в новый список fileList. таким образом содержимое исходных списков (т.е. filpaths) становится частью большего списка fileList.
пример использования
filePathList1 = [‘path1’, ‘path2’, ‘path3’] filePathList2 = [‘path4’, ‘path5’, ‘path6’] load_files(filePathList1, filePathList2) Output → [‘path1’, ‘path2’, ‘path3’, ‘path4’, ‘path5’, ‘path6’] def load_files(fileNames: tuple): fileList = [] for file in fileNames: fileList.append(file) return fileList
Затем мы вызываем функцию с предпочтительным каталогом, в котором хранятся исследовательские работы.
files = load_files((biorxivMedrxiv.iterdir()))
Вы можете заметить, что я не передал biorxivMedrxiv
как есть. Причина в том, что biorxivMedrxiv
— это объект «posixpath», а не итерируемый объект. Чтобы перебрать biorxivMedrxiv
, я использовал метод iterdir()
, который дает нам итератор.
Пути к файлам, обработанные с помощью load_files()
, содержат объекты пути и могут не подходить для определенных операций.
Например, объекты пути нельзя повторять, так как содержимое, возвращаемое load_files()
, является объектами «posixpath».
Мы преобразуем эти объекты пути в их строковые «пути» с помощью функции filePath()
. Эта функция делает следующее →
- Принимает в списке объекты пути.
- Перебирает список и преобразует каждый элемент списка, т. е. объекты пути, в строковый формат.
- Затем он помещает эти строковые пути в список файловых путей.
Примечание. Для функции load_files()
я передал в кортеже списка файлов значение iterdir()
, чтобы мы могли перебирать эти пути. Итак, я думаю, что содержимое, возвращаемое функцией load_files()
, можно повторять без использования функции filePath()
. Однако я еще не проверял это.
def filePath(files): filePath = [] for file in files: filePath.append(str(file)) return filePath
Затем мы вызываем функцию.
filePaths=filePath(files)
getRawFiles()
функция --›
- Принимает список путей к файлам
- Проходит по каждому пути к файлу из списка
files
и использует методjson.load()
для чтения содержимого. - наконец, он добавляет этот объект json в список
rawFiles
.
def getRawFiles(files: list): rawFiles = [] for fileName in files: rawFile = json.load(open(fileName, 'rb')) rawFiles.append(rawFile) return rawFiles
Вызов этой функции даст нам необработанное содержимое файлов json.
rawFiles = getRawFiles(filePaths)
format_name()
→
- Принимает объект json с автором в качестве ключа.
- Соединяет имена в следующей последовательности ‹имя› ‹отчество› ‹фамилия.
- Функция также позаботится о том, что если отчества нет, то просто введите ‹имя› ‹фамилия
format_affiliation()
→
- Принимает объект json с ключом в качестве принадлежности.
- Если сведения о местоположении есть в json, поместите их в список и верните.
- Если учреждение есть, соедините данные о местонахождении и учреждении и поместите их в список.
format_authors()
→
- Принимает объект json с ключом в качестве автора.
- Соединяет имя автора с аффилиациями, если аффилиации доступны.
format_body()
→
- Принимает объект json с ключом как body_text.
- Извлекает текст, а затем добавляет его в список.
format_bib()
→
- Принимает объект json с ключом в качестве нагрудника.
- Объединяет «название», «авторы», «место проведения», «год» вместе, чтобы сформировать строку.
def format_name(author): middle_name = " ".join(author['middle']) if author['middle']: return " ".join([author['first'], middle_name, author['last']]) else: return " ".join([author['first'], author['last']]) def format_affiliation(affiliation): text = [] location = affiliation.get('location') if location: text.extend(list(affiliation['location'].values())) institution = affiliation.get('institution') if institution: text = [institution] + text return ", ".join(text) def format_authors(authors, with_affiliation=False): name_ls = [] for author in authors: name = format_name(author) if with_affiliation: affiliation = format_affiliation(author['affiliation']) if affiliation: name_ls.append(f"{name} ({affiliation})") else: name_ls.append(name) else: name_ls.append(name) return ", ".join(name_ls) def format_body(body_text): texts = [(di['section'], di['text']) for di in body_text] texts_di = {di['section']: "" for di in body_text} for section, text in texts: texts_di[section] += text body = "" for section, text in texts_di.items(): body += section body += "\n\n" body += text body += "\n\n" return body def format_bib(bibs): if type(bibs) == dict: bibs = list(bibs.values()) bibs = deepcopy(bibs) formatted = [] for bib in bibs: bib['authors'] = format_authors( bib['authors'], with_affiliation=False ) formatted_ls = [str(bib[k]) for k in ['title', 'authors', 'venue', 'year']] formatted.append(", ".join(formatted_ls)) return "; ".join(formatted)
generate_clean_df()
-->
- Использует вышеуказанные вспомогательные функции для создания кадра данных pandas.
- Результирующий фрейм данных будет иметь следующие столбцы: «paper_id», «название», «авторы», «принадлежности», «аннотация», «текст», «библиография», «raw_authors», «raw_bibliography».
def generate_clean_df(all_files): cleaned_files = [] for file in all_files: features = [ file['paper_id'], file['metadata']['title'], format_authors(file['metadata']['authors']), format_authors(file['metadata']['authors'], with_affiliation=True), format_body(file['abstract']), format_body(file['body_text']), format_bib(file['bib_entries']), file['metadata']['authors'], file['bib_entries'] ] cleaned_files.append(features) col_names = ['paper_id', 'title', 'authors', 'affiliations', 'abstract', 'text', 'bibliography','raw_authors','raw_bibliography'] clean_df = pd.DataFrame(cleaned_files, columns=col_names) clean_df.head() return clean_df
Затем вызов этой функции сгенерирует наш фрейм данных.
pd.set_option('display.max_columns', None) cleanDf = generate_clean_df(rawFiles) cleanDf.head()
Наконец, сохраните фрейм данных в файл csv. Поскольку весь предыдущий процесс занимает немного времени, поэтому сохранение файла в csv сэкономит время позже.
cleanDf.to_csv(outputPath/'cleandf.csv')
Данные
Затем мы создаем пакет данных из csv, который мы создали из файлов json.
createDataBunchForLanguageModel() — это вспомогательная функция. Это делает следующее →
- Создает пакет данных из CSV-файла.
- Данные в пакете данных извлекаются из столбца «текст» CSV-файла.
- 10% данных зарезервированы для проверки.
- Поскольку языковая модель, которую мы создадим в дальнейшем, будет моделью с самоконтролем. Он получает метку из самих данных.
- Таким образом, label_for_lm() помогает нам в этом.
- После создания пакета данных он сохраняется в файле pickle.
- В следующий раз, когда этот код запустится, нам не нужно снова проходить процесс создания пакета данных. Мы можем использовать файл pickle.
Чтобы узнать больше о databunch, обратитесь к моей предыдущей статье здесь.
def createDataBunchForLanguageModel(outputPath: Path, csvName: str, textCol: str, pickelFileName: str, splitBy: float, batchSize: int): data_lm = TextList.from_csv(outputPath, f'{csvName}.csv', cols=textCol)\ .split_by_rand_pct(splitBy)\ .label_for_lm()\ .databunch(bs=batchSize) data_lm.save(f'{pickelFileName}.pkl')
Вызов этой функции для создания пакета данных.
createDataBunchForLanguageModel(outputPath, 'cleandf', 'text', 'cleanDf', 0.1, 48)
loadData()
— это вспомогательная функция для загрузки файла пакета данных. Он принимает требуемый размер пакета, который мы хотим загрузить из пакета данных.
def loadData(outputPath: Path, databunchFileName: str, batchSize: int, showBatch: bool= False): data_lm = load_data(outputPath, f'{databunchFileName}.pkl', bs=batchSize) if showBatch: data_lm.show_batch() return data_lm
Мы будем использовать эту функцию в ближайшее время.
Развитие ученика
Мы используем ULMFIT для создания языковой модели в корпусе исследовательских данных, а затем тонко настраиваем эту языковую модель.
Мы создаем языковую модель учащегося. Language_model_learner() — это быстрый метод, который помогает нам создать объект учащегося с данными, созданными в предыдущих разделах.
Здесь мы используем размер пакета 48. Этот учащийся построен с использованием архитектуры предварительно обученной языковой модели AWD_LSTM. Это самоконтролируемая часть. Это модель, которая была обучена на корпусе английского языка для предсказания следующей последовательности предложений и, таким образом, понимает структуру языка.
Нам просто нужно точно настроить эту модель в нашем корпусе данных, который затем можно использовать для создания классификатора.
Я еще не строю классификатор, потому что понятия не имею, что нам нужно классифицировать. Тем не менее, я считаю, что эта точно настроенная языковая модель может быть адаптирована для поиска скрытой информации в большом корпусе исследовательских работ, которые в противном случае были бы скрыты/пропущены читателями-людьми.
Примечание.Я не буду углубляться в ULMFIT, так как это не является целью этого поста. Возможно позже сделаю подробный пост. Однако подробное объяснение ULMFIT можно найти здесь на fastai.
learner = language_model_learner(loadData(outputPath, 'cleanDf', 48, showBatch= False), AWD_LSTM, drop_mult=0.3)
Мы наносим скорость обучения для нашей языковой модели. На этом графике мы попытаемся найти наилучшую скорость обучения, подходящую для нашей модели. plotLearningRate()
— это вспомогательная функция, которая отображает для нас скорость обучения.
Объяснение того, как использовать скорость обучения, можно найти здесь.
def plotLearningRate(learner, skip_end): learner.lr_find() learner.recorder.plot(skip_end=skip_end)
Теперь мы наносим скорость обучения.
plotLearningRate(learner, 15)
Затем мы принимаем эту скорость обучения в качестве отправной точки, где сюжет расходится. Затем модель обучается с помощью политики «подгонка одного цикла» с этой начальной скоростью обучения.
Здесь мы тренируем только голову.
learner.fit_one_cycle(1, 1e-02, moms=(0.8,0.7))
Поскольку мы создаем языковую модель, мы не слишком беспокоимся о том, чтобы получить здесь максимально возможную точность. Размораживаем сеть и обучаем еще.
learner.unfreeze() learner.fit_one_cycle(10,1e-3, moms=(0.8,0.7))
Наконец, мы сохраняем точно настроенную языковую модель для последующего использования в тестировании/прогнозировании.
learner.save('fineTuned')
Тестирование
Давайте посмотрим, сможет ли модель подключить информацию/знания из корпуса. Сначала мы загружаем сохраненную модель, а затем пытаемся найти прогноз для поискового запроса.
learner = learner.load('fineTuned')
Давайте возьмем тестовое предложение в качестве входных данных.
TEXT = "Range of incubation periods for the disease in humans" N_WORDS = 40 N_SENTENCES = 2
Мы увидим, сможет ли наша модель предсказать слова, связанные с нашей тестовой строкой.
print("\n".join(learner.predict(TEXT, N_WORDS, temperature=0.75) for _ in range(N_SENTENCES)))
Сейчас у нас есть довольно хорошая языковая модель. Теперь, когда мы точно настроили нашу языковую модель, мы можем сохранить модель, предсказывающую следующую последовательность слов, и кодировщик, отвечающий за создание и обновление скрытых состояний.
Сохраненную модель можно использовать для переноса обучения в дальнейших задачах, а сохраненный кодировщик можно использовать для создания классификатора для текстовой классификации любого помеченного набора данных, связанного с COVID-19.
Мы будем использовать .save()
для сохранения файла .pth
.
Упомянутая ниже функция save()
является вспомогательной функцией, которая может сохранять только модель или кодировщик и модель в отдельных файлах.
def save(learner, saveEncoder: bool = True): if saveEncoder: learner.save_encoder('fine_tuned_encoder') learner.save('fine_tuned_model')
вызов этой функции сделает экономию за вас.
save(learner)
Конечные примечания
Я надеюсь, что это ядро и эта языковая модель будут полезны для кого-то, кто может работать с этим набором данных или любыми данными, связанными с COVID-19.
Я надеюсь, что кто-то, кто более опытен в НЛП и глубоком обучении, улучшит эту модель и сделает что-то полезное, что затем может способствовать борьбе с COVID-19.
Я никоим образом не являюсь экспертом в НЛП, поэтому сеть, которую я разработал здесь, основана на коде из урока 3 курса — Практика глубокого обучения для кодеров от fastai».
Я надеюсь, что кто-то, кто более опытен в НЛП и глубоком обучении, улучшит эту модель и выведет что-то полезное, что затем может помочь нам в борьбе с COVID-19.
Код, использованный в этом посте, доступен в виде ядра kaggle здесь и был отправлен в рамках одной из задач задачи Данные исследования COVID-19.