CoderCastrov logo
CoderCastrov
Анализ данных

Парсинг больших данных из общедоступных исследовательских репозиториев, например PubMed, arXiv (2/)

Парсинг больших данных из общедоступных исследовательских репозиториев, например PubMed, arXiv (2/)
просмотров
6 мин чтение
#Анализ данных

Это часть 2 серии "Twitter-бот для отслеживания академической подобласти исследований". Вам понадобится базовое владение языком Python.

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

Парсинг данных с PubMed

PubMed - удивительный, гигантский источник метаданных, в основном посвященных биомедицинским исследованиям. (Открытые статьи можно найти в связанной, но не следует путать базе данных PubMed Central.) Помимо поисковой системы, PubMed также предоставляет мощный набор "E-утилит", которые служат API для исследователей, позволяя им бесплатно загружать большие объемы данных. Запросы выполняются с помощью HTTP-запросов, то есть URL, который вы вводите в браузер, возвращает запрошенные данные в чистом XML-формате. Этот протокол определен в Протоколе открытых архивов для сбора метаданных v.2.0 (OAI-PMH 2.0). (arXiv использует точно такой же протокол.)

На Python вы можете использовать библиотеку requests для выполнения HTTP-запросов, или вы можете использовать библиотеку Biopython, которая включает модуль Entrez, предназначенный для запросов к базам данных Национального центра биотехнологической информации (NCBI), включая PubMed и PubMed Central.

Некоторые из нижеприведенного были вдохновлены этим отличным блогом Марко Бонзанини и его кодовым фрагментом здесь:

Метод esearch возвращает список уникальных идентификаторов (UID), которые затем передаются методу efetch, который затем возвращает запрошенные метаданные для каждого из UID. esearch - это мощный инструмент с несколькими параметрами, о которых вы можете узнать больше здесь. Для парсинга большого количества статей просто установите retmax на нужное значение - я пробовал до 15000 без проблем. Вы также можете установить параметр retstart, чтобы вернуть результаты, начиная с определенного индекса.

efetch немного капризен. Парсер Entrez.read иногда сталкивается с ошибками, когда XML, возвращаемый efetch, не находится в правильном формате (вы можете найти информацию об этом, введя в поисковик "pubmed dtd", но мне никогда не приходилось разбираться в этом). Парсер также не нравится слишком длинный XML-ввод, например, для тысяч документов. Рекомендую загружать данные порциями по 50-100 UID. Например, расширяя код из фрагмента,

results = search('fever') # или любой другой запрос, который вам нравится
id_list = results['IdList'] # список UID
chunk_size = 50 # любое значение, которое вам нравится
for chunk_i in range(0, len(id_list), chunk_size):
    chunk = id_list[chunk_i:chunk_i + chunk_size]
    try: 
        papers = fetch_details(chunk)
        for i, paper in enumerate(papers['PubmedArticle']):
            do_something() # возможно, записать в файл CSV
    except: # иногда порция может вызвать ошибку парсера
        pass

Парсинг с arXiv

arXiv - это еще один гигантский репозиторий статей, в основном по физике и компьютерным наукам, где люди публикуют статьи до их рецензирования и публикации в журнале (известные как "препринты"). Эта модель быстро становится предпочтительной для распространения результатов исследований в своевременном и открытом доступе. Также существует связанный сервер предварительных печатей под названием biorXiv, который обслуживает сообщество биологических наук. arXiv использует тот же протокол OAI-PMH, поэтому процесс очень похож. Также существуют библиотеки Python для парсинга, такие как здесь и здесь. Я не использовал библиотеку для этого, потому что выполнение HTTP-запроса через Python казалось достаточно простым, и я хотел попробовать это. Руководство пользователя API здесь отличное.

Главная страница Arxiv.

Небольшое отличие от PubMed заключается в том, что поиск в arXiv, кажется, не имеет опции возвращения UID. Он просто возвращает XML. Что еще лучше в arXiv, так это то, что XML представлен в стандартном формате Atom 1.0, чистом и признанном формате, который может быть интерпретирован практически всеми XML-парсерами. В конечном итоге я использовал тот же парсер, который использую для разбора лент RSS, отличную библиотеку [feedparser](https://pythonhosted.org/feedparser/introduction.html#parsing-a-feed-from-a-remote-url).

import feedparsern_papers = 30000
chunk_size = 50
category = 'physics.optics'for chunk_i in range(0, n_papers, chunk_size): 
   feed = feedparser.parse('[http://export.arxiv.org/api/query?search_query=cat:%s&start=%d&max_results=%d'](http://export.arxiv.org/api/query?search_query=cat%3Aphysics.optics&start=%25d&max_results=%25d%27) % (category, chunk_i,chunk_size))
   
   for i in range(len(feed.entries)):
       entry = feed.entries[i]
       title = (entry.title).replace('\n', "") #удаляет переносы строк
       сделать_что-то() # возможно, записать в файл CSV

Если вы забредете в эту статью, ища помощи в парсинге PubMed или arXiv, у вас теперь есть вся необходимая информация. Если вы следуете серии статей о моем боте Twitter, продолжайте читать!

Я хотел спарсить заголовки статей, которые затем можно использовать для обучения классификатора. Классификатор будет размечать 3 класса - 0: не оптика, 1: не биомедицинская оптика и 2: биомедицинская оптика. Я также хотел обучить свою модель на максимально возможном количестве данных, вероятно, десятки тысяч заголовков. Неудивительно, что я был гораздо менее взволнован перспективой ручной разметки десятков тысяч заголовков.

Моя стратегия заключалась в использовании поиска PubMed по релевантности (параметр sort='relevance' в esearch), чтобы попытаться автоматически сгруппировать заголовки. Класс 0 будет представлять собой разнообразие научных/инженерных статей. Класс 1 будет статьями об оптике, надеюсь, с физическим/инженерным уклоном. И класс 2 будет биомедицинской оптикой. В моей первой попытке я использовал следующие запросы в поиске:

groups = [['физика', 'биология','инженерия'],
['физика оптики','инженерия оптики'],
['биомедицинская оптика', 'биофотоника']]for group in groups:
    label = groups.index(group) # группа 0 запросов помечена 0
    if groups.index(group)==0:
        retmax=4000
    else:
        retmax = 6000    for query in group:
        search(query,retmax) #переписать функцию для принятия retmax
        ...
        save_titles_with_label(label)

Я сохранил около 12 000 заголовков в каждом классе. Итоговая обученная модель оказалась неплохой (~70% точность, см. следующую статью) и была использована в первой версии моего бота. Однако хорошее напоминание здесь - всегда тщательно изучайте свои данные перед обучением модели. Когда я, наконец, решил посмотреть на собранные мной данные, я раздраженно обнаружил, что мои данные класса 1 содержат много статей о биофотонике. Это действительно не должно было вызывать удивление, потому что база данных - PubMed, где статьи в основном финансируются Национальными институтами здравоохранения и имеют существенную биомедицинскую направленность.

Эти шумные метки также означали, что мои оценки точности будут неверными. Заголовки, такие как "High-efficiency femtosecond ablation of silicon with GHz repetition rate laser source", не имеющие отношения к биофотонике, были классифицированы как 69% класс 2, а "Quantifying cellular forces and biomechanical properties by correlative micropillar traction force and Brillouin microscopy", практически квинтэссенция биофотоники, была 22% классом 2 - довольно драматические неудачи. Я кратко поиграл с идеей "быстро" просмотра моих данных класса 1 и исправления меток - я успел проверить 200 заголовков за 15 минут. По такой скорости (~5 секунд на заголовок) мне потребуется 15 часов. Нет, спасибо.

Затем я понял, что категория physics.optics в arXiv была бы идеальным классом 1 (не биомедицинская оптика) данными. В последние годы люди начали публиковать там также статьи о биофотонике, но они составляют крошечную долю статей. Поэтому для класса 1 я извлек около 20 000 статей из physics.optics. Для моих данных класса 2 я использовал запросы PubMed "биомедицинская оптика", "биофотоника", "биологическая оптика" и "биомедицинская микроскопия". Последний запрос немного неоднозначен, поскольку многие методы микроскопии являются неоптическими, но я хотел добавить более сильный микроскопический оттенок (для большой аудитории в Twitter в этой нише) и больше разнообразия из результатов "биофотоники", которые были доминированы статьями о оптической когерентной томографии и фотоакустической томографии. Я также не учел повторяющиеся заголовки в этих собранных поисках, главным образом потому, что у меня уже было недостаточно примеров в классе 2, и я не хотел слишком сокращать.

Данные, которые я в конечном итоге использовал для модели.

Для каждой найденной статьи я записал строку данных в файл csv. Иногда некоторые заголовки, особенно из arXiv, содержат символы Unicode, которые вызывают проблемы при записи в csv и/или интерпретации людьми/ML. Есть хорошая библиотека unidecode, которую можно установить с помощью pip install unidecode и которая преобразует символы Unicode в читаемый формат.

import csv
from unidecode import unidecode
...with open('data.csv', mode='w') as data_file:
    file_writer =  csv.writer(data_file, delimiter=',', quotechar='"', quoting=csv.QUOTE_MINIMAL)
    for paper in papers:
        file_writer.writerow([unidecode(title), str(class_label)])

Код для этого раздела находится в файле scrape_from_pubmed_arxiv.py, скоро будет размещен на Github.