Как сгруппировать категории википедии в Python? - PullRequest
0 голосов
/ 11 февраля 2019

Для каждой концепции моего набора данных я сохранил соответствующие категории википедии.Например, рассмотрим следующие 5 концепций и соответствующие им категории википедии.

  • гипертриглицеридемия: ['Category:Lipid metabolism disorders', 'Category:Medical conditions related to obesity']
  • ингибитор фермента: ['Category:Enzyme inhibitors', 'Category:Medicinal chemistry', 'Category:Metabolism']
  • шунтирование: ['Category:Surgery stubs', 'Category:Surgical procedures and techniques']
  • Перт: ['Category:1829 establishments in Australia', 'Category:Australian capital cities', 'Category:Metropolitan areas of Australia', 'Category:Perth, Western Australia', 'Category:Populated places established in 1829']
  • Климат: ['Category:Climate', 'Category:Climatology', 'Category:Meteorological concepts']

Как видите, первые три концепции относятся к медицинской сфере (тогда как остальныедва термина не являются медицинскими терминами).

Точнее, я хочу разделить мои понятия как медицинские и немедицинские.Однако очень сложно разделить понятия, используя только категории.Например, хотя два понятия enzyme inhibitor и bypass surgery относятся к медицинской сфере, их категории сильно отличаются друг от друга.

Поэтому я хотел бы знать, есть ли способ получитьparent category категорий (например, категории enzyme inhibitor и bypass surgery относятся к medical родительской категории)

В настоящее время я использую pymediawiki и pywikibot.Тем не менее, я не ограничен только этими двумя библиотеками и рад иметь решения, использующие и другие библиотеки.

EDIT

По предложению @IlmariKaronen я также используюcategories of categories и полученные результаты выглядят следующим образом (T мелкий шрифт рядом с category - categories of the category).enter image description here

Однако я все еще не мог найти способ использовать данные категории, чтобы решить, является ли данный термин медицинским или немедицинским.

Более того, как указывает @IlmariKaronen, использование Wikiproject деталей может быть возможным.Однако, похоже, что Medicine википроект не имеет всех медицинских терминов.Поэтому нам также необходимо проверить и другие википроекты.

РЕДАКТИРОВАТЬ: Мой текущий код извлечения категорий из концепций Википедии выглядит следующим образом.Это можно сделать, используя pywikibot или pymediawiki следующим образом.

  1. Используя библиотеку pymediawiki

    import mediawiki как pw

    p = wikipedia.page('enzyme inhibitor')
    print(p.categories)
    
  2. Использование библиотеки pywikibot

    import pywikibot as pw
    
    site = pw.Site('en', 'wikipedia')
    
    print([
        cat.title()
        for cat in pw.Page(site, 'support-vector machine').categories()
        if 'hidden' not in cat.categoryinfo
    ])
    

Категории категорий также можно выполнить так же, как показано в ответе @IlmariKaronen.

Если вы ищете более длинный список концепций для тестирования, я привел несколько примеров ниже.

['juvenile chronic arthritis', 'climate', 'alexidine', 'mouthrinse', 'sialosis', 'australia', 'artificial neural network', 'ricinoleic acid', 'bromosulfophthalein', 'myelosclerosis', 'hydrochloride salt', 'cycasin', 'aldosterone antagonist', 'fungal growth', 'describe', 'liver resection', 'coffee table', 'natural language processing', 'infratemporal fossa', 'social withdrawal', 'information retrieval', 'monday', 'menthol', 'overturn', 'prevailing', 'spline function', 'acinic cell carcinoma', 'furth', 'hepatic protein', 'blistering', 'prefixation', 'january', 'cardiopulmonary receptor', 'extracorporeal membrane oxygenation', 'clinodactyly', 'melancholic', 'chlorpromazine hydrochloride', 'level of evidence', 'washington state', 'cat', 'newyork', 'year elevan', 'trituration', 'gold alloy', 'hexoprenaline', 'second molar', 'novice', 'oxygen radical', 'subscription', 'ordinate', 'approximal', 'spongiosis', 'ribothymidine', 'body of evidence', 'vpb', 'porins', 'musculocutaneous']

Для очень длинного списка, пожалуйста, перейдите по ссылке ниже.https://docs.google.com/document/d/1BYllMyDlw-Rb4uMh89VjLml2Bl9Y7oUlopM-Z4F6pN0/edit?usp=sharing

ПРИМЕЧАНИЕ. Я не ожидаю, что решение будет работать на 100% (если предложенный алгоритм способен обнаружить многие медицинские концепции, которых мне достаточно)

Я с удовольствием предоставлю более подробную информацию, если это необходимо.

Ответы [ 6 ]

0 голосов
/ 20 февраля 2019

Библиотека wikipedia также является хорошим выбором для извлечения категорий с заданной страницы, так как wikipedia.WikipediaPage(page).categories возвращает простой список.Библиотека также позволяет выполнять поиск по нескольким страницам, если все они имеют одинаковый заголовок.

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

import wikipedia

def categorySorter(targetCats, pagesToCheck, mainCategory):
    targetList = []
    nonTargetList = []
    targetCats = [i.lower() for i in targetCats]

    print('Sorting pages...')
    print('Sorted:', end=' ', flush=True)
    for page in pagesToCheck:

        e = openPage(page)

        def deepList(l):
            for item in l:
                if item[1] == 'SUBPAGE_ID':
                    deepList(item[2])
                else:
                    catComparator(item[0], item[1], targetCats, targetList, nonTargetList, pagesToCheck[-1])

        if e[1] == 'SUBPAGE_ID':
            deepList(e[2])
        else:
            catComparator(e[0], e[1], targetCats, targetList, nonTargetList, pagesToCheck[-1])

    print()
    print()
    print('Results:')
    print(mainCategory, ': ', targetList, sep='')
    print()
    print('Non-', mainCategory, ': ', nonTargetList, sep='')

def openPage(page):
    try:
        pageList = [page, wikipedia.WikipediaPage(page).categories]
    except wikipedia.exceptions.PageError as p:
        pageList = [page, 'NONEXIST_ID']
        return
    except wikipedia.exceptions.DisambiguationError as e:
        pageCategories = []
        for i in e.options:
            if '(disambiguation)' not in i:
                pageCategories.append(openPage(i))
        pageList = [page, 'SUBPAGE_ID', pageCategories]
        return pageList
    finally:
        return pageList

def catComparator(pageTitle, pageCategories, targetCats, targetList, nonTargetList, lastPage):

    # unhash to view the categories of each page
    #print(pageCategories)
    pageCategories = [i.lower() for i in pageCategories]

    any_in = False
    for i in targetCats:
        if i in pageTitle:
            any_in = True
    if any_in:
        print('', end = '', flush=True)
    elif compareLists(targetCats, pageCategories):
        any_in = True

    if any_in:
        targetList.append(pageTitle)
    else:
        nonTargetList.append(pageTitle)

    # Just prints a pretty list, you can comment out until next hash if desired
    if any_in:
        print(pageTitle, '(T)', end='', flush=True)
    else:
        print(pageTitle, '(F)',end='', flush=True)

    if pageTitle != lastPage:
        print(',', end=' ')
    # No more commenting

    return any_in

def compareLists (a, b):
    for i in a:
        for j in b:
            if i in j:
                return True
    return False

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

medicalCategories = ['surgery', 'medic', 'disease', 'drugs', 'virus', 'bact', 'fung', 'pharma', 'cardio', 'pulmo', 'sensory', 'nerv', 'derma', 'protein', 'amino', 'unii', 'chlor', 'carcino', 'oxi', 'oxy', 'sis', 'disorder', 'enzyme', 'eine', 'sulf']
listOfPages = ['juvenile chronic arthritis', 'climate', 'alexidine', 'mouthrinse', 'sialosis', 'australia', 'artificial neural network', 'ricinoleic acid', 'bromosulfophthalein', 'myelosclerosis', 'hydrochloride salt', 'cycasin', 'aldosterone antagonist', 'fungal growth', 'describe', 'liver resection', 'coffee table', 'natural language processing', 'infratemporal fossa', 'social withdrawal', 'information retrieval', 'monday', 'menthol', 'overturn', 'prevailing', 'spline function', 'acinic cell carcinoma', 'furth', 'hepatic protein', 'blistering', 'prefixation', 'january', 'cardiopulmonary receptor', 'extracorporeal membrane oxygenation', 'clinodactyly', 'melancholic', 'chlorpromazine hydrochloride', 'level of evidence', 'washington state', 'cat', 'year elevan', 'trituration', 'gold alloy', 'hexoprenaline', 'second molar', 'novice', 'oxygen radical', 'subscription', 'ordinate', 'approximal', 'spongiosis', 'ribothymidine', 'body of evidence', 'vpb', 'porins', 'musculocutaneous']
categorySorter(medicalCategories, listOfPages, 'Medical')

Этот пример списка получает ~ 70% от того, что должно быть в списке, по крайней мере, длямои знания.

0 голосов
/ 19 февраля 2019

Обзор решения

Хорошо, я бы подошел к проблеме с разных сторон.Здесь есть несколько замечательных предложений, и на вашем месте я бы использовал ансамбль этих подходов (большинство голосов, метка прогнозирования, которая согласована более чем с 50% классификаторов в вашем двоичном случае).

Я думаю о следующих подходах:

  • Активное обучение (пример подхода предоставлен мной ниже)
  • MediaWikiобратные ссылки предоставлены в качестве ответа @ TavoGC
  • SPARQL категории предков, предоставленные в качестве комментария к вашему вопросу @ Stanislav Kralin и / или родительские категории , предоставленные @ Мина Нагараджан (эти два могут быть ансамблем самостоятельно, исходя из их различий, но для этого вам придется связаться с обоими создателями исравните их результаты).

Таким образом, 2 из 3 должны согласиться с тем, что определенная концепция является медицинской, что сводит к минимуму вероятность ошибки в дальнейшем.

Пока мына это я быаргументируйте против подхода, представленного @ananand_v.singh в этот ответ , потому что:

  • метрика расстояния не должна евклидово, косинусное сходство является гораздо лучшей метрикой (используется, например, spaCy ), поскольку оно не учитывает величину векторов (и не должно, именно так обучались word2vec или GloVe)
  • было бы создано много искусственных скоплений, если бы я правильно понял, а нам нужно только два: лекарственное и немедицинское.Кроме того, центр тяжести лекарства не сосредоточен на самом лекарстве.Это создает дополнительные проблемы, скажем, что центроид удален от медицины, и другие слова, такие как, скажем, computer или human (или любое другое, не подходящее, по вашему мнению, к медицине) могут попасть в кластер.
  • сложно оценить результаты, тем более, дело строго субъективное.Кроме того, векторы слов трудно визуализировать и понять (приведение их к более низким измерениям [2D / 3D] с использованием PCA / TSNE / подобных для многих слов) даст нам совершенно бессмысленные результаты [да, я пытался это сделать, PCAполучает около 5% объясненной дисперсии для вашего более длинного набора данных, действительно, очень низкий]).

Исходя из вышеперечисленных проблем, я нашел решение с использованием active learning , котороеЭто довольно забытый подход к таким задачам.

Активный подход к обучению

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

Кодировка знаний

Как anand_v.singh отметил, что словосочетания являются одним из наиболее многообещающих подходов, и я буду использовать его и здесь (хотя и иначе, и IMO гораздо чище и проще).sier fashion).

Я не собираюсь повторять его пункты в своем ответе, поэтому я добавлю свои два цента:

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

Измерение сходства с использованием spaCy

Этот класс измеряет сходство между medicine, закодированным как вектор слов GloCe spaCy, и любым другим понятием.

class Similarity:
    def __init__(self, centroid, nlp, n_threads: int, batch_size: int):
        # In our case it will be medicine
        self.centroid = centroid

        # spaCy's Language model (english), which will be used to return similarity to
        # centroid of each concept
        self.nlp = nlp
        self.n_threads: int = n_threads
        self.batch_size: int = batch_size

        self.missing: typing.List[int] = []

    def __call__(self, concepts):
        concepts_similarity = []
        # nlp.pipe is faster for many documents and can work in parallel (not blocked by GIL)
        for i, concept in enumerate(
            self.nlp.pipe(
                concepts, n_threads=self.n_threads, batch_size=self.batch_size
            )
        ):
            if concept.has_vector:
                concepts_similarity.append(self.centroid.similarity(concept))
            else:
                # If document has no vector, it's assumed to be totally dissimilar to centroid
                concepts_similarity.append(-1)
                self.missing.append(i)

        return np.array(concepts_similarity)

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

import json
import typing

import numpy as np
import spacy

nlp = spacy.load("en_vectors_web_lg")

centroid = nlp("medicine")

concepts = json.load(open("concepts_new.txt"))
concepts_similarity = Similarity(centroid, nlp, n_threads=-1, batch_size=4096)(
    concepts
)

Вы можете заменить свои данные вместо new_concepts.json.

Посмотрите на spacy.load и обратите внимание, что я использовал en_vectors_web_lg.Он состоит из 685.000 уникальных векторов слов (что очень много) и может работать из коробки для вашего случая.Вы должны загрузить его отдельно после установки spaCy, более подробную информацию можно получить по ссылкам выше.

Дополнительно Вы можете использовать несколько слов центроида , например, добавить такие слова, какdisease или health и усредните их векторы слов.Я не уверен, что это положительно скажется на вашем случае.

Другая возможность может заключаться в использовании нескольких центроидов и вычислении подобия между каждой концепцией и множеством центроидов.У нас может быть несколько порогов в этом случае, это может удалить некоторые ложных срабатываний , но может пропустить некоторые условия, которые можно считать похожими на medicine.Более того, это значительно усложнит ситуацию, но если ваши результаты неудовлетворительны, вы должны рассмотреть два варианта выше (и только в этом случае не переходите к этому подходу без предварительной мысли).

Теперь мы имеемгрубая мера сходства понятий.Но что означает , что определенное понятие имеет 0,1 положительного сходства с медициной?Это понятие следует классифицировать как медицинский?Или, может быть, это уже слишком далеко?

Задание эксперта

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

Я написал класс с интерфейсом sklearn-like, в котором человеку предлагалось классифицировать понятия до оптимального порога (или максимального числаитераций).

class ActiveLearner:
    def __init__(
        self,
        concepts,
        concepts_similarity,
        max_steps: int,
        samples: int,
        step: float = 0.05,
        change_multiplier: float = 0.7,
    ):
        sorting_indices = np.argsort(-concepts_similarity)
        self.concepts = concepts[sorting_indices]
        self.concepts_similarity = concepts_similarity[sorting_indices]

        self.max_steps: int = max_steps
        self.samples: int = samples
        self.step: float = step
        self.change_multiplier: float = change_multiplier

        # We don't have to ask experts for the same concepts
        self._checked_concepts: typing.Set[int] = set()
        # Minimum similarity between vectors is -1
        self._min_threshold: float = -1
        # Maximum similarity between vectors is 1
        self._max_threshold: float = 1

        # Let's start from the highest similarity to ensure minimum amount of steps
        self.threshold_: float = 1
  • samples аргумент описывает, сколько примеров будет показано эксперту во время каждой итерации (это максимум, он вернет меньше, если образцы уже были запрошеныили их недостаточно для отображения).
  • step представляет падение порога (мы начинаем с 1, что означает абсолютное сходство) в каждой итерации.
  • change_multiplier - есликонцепции ответов экспертов не связаны (или, по большей части, не связаны, так как возвращаются несколько из них), шаг умножается на это число с плавающей запятой.Он используется для точного определения точного порога между step изменениями на каждой итерации.
  • понятия сортируются по их сходству (чем больше сходство понятий, тем выше)

Функцияниже запрашивает мнение эксперта и находит оптимальный порог на основе его ответов.

def _ask_expert(self, available_concepts_indices):
    # Get random concepts (the ones above the threshold)
    concepts_to_show = set(
        np.random.choice(
            available_concepts_indices, len(available_concepts_indices)
        ).tolist()
    )
    # Remove those already presented to an expert
    concepts_to_show = concepts_to_show - self._checked_concepts
    self._checked_concepts.update(concepts_to_show)
    # Print message for an expert and concepts to be classified
    if concepts_to_show:
        print("\nAre those concepts related to medicine?\n")
        print(
            "\n".join(
                f"{i}. {concept}"
                for i, concept in enumerate(
                    self.concepts[list(concepts_to_show)[: self.samples]]
                )
            ),
            "\n",
        )
        return input("[y]es / [n]o / [any]quit ")
    return "y"

Пример вопроса выглядит так:

Are those concepts related to medicine?                                                      

0. anesthetic drug                                                                                                                                                                         
1. child and adolescent psychiatry                                                                                                                                                         
2. tertiary care center                                                     
3. sex therapy                           
4. drug design                                                                                                                                                                             
5. pain disorder                                                      
6. psychiatric rehabilitation                                                                                                                                                              
7. combined oral contraceptive                                
8. family practitioner committee                           
9. cancer family syndrome                          
10. social psychology                                                                                                                                                                      
11. drug sale                                                                                                           
12. blood system                                                                        

[y]es / [n]o / [any]quit y

... парсинг ответа эксперта:

# True - keep asking, False - stop the algorithm
def _parse_expert_decision(self, decision) -> bool:
    if decision.lower() == "y":
        # You can't go higher as current threshold is related to medicine
        self._max_threshold = self.threshold_
        if self.threshold_ - self.step < self._min_threshold:
            return False
        # Lower the threshold
        self.threshold_ -= self.step
        return True
    if decision.lower() == "n":
        # You can't got lower than this, as current threshold is not related to medicine already
        self._min_threshold = self.threshold_
        # Multiply threshold to pinpoint exact spot
        self.step *= self.change_multiplier
        if self.threshold_ + self.step < self._max_threshold:
            return False
        # Lower the threshold
        self.threshold_ += self.step
        return True
    return False

И, наконец, целый кодовый код ActiveLearner, который, по мнению эксперта, находит оптимальный порог подобия:

class ActiveLearner:
    def __init__(
        self,
        concepts,
        concepts_similarity,
        samples: int,
        max_steps: int,
        step: float = 0.05,
        change_multiplier: float = 0.7,
    ):
        sorting_indices = np.argsort(-concepts_similarity)
        self.concepts = concepts[sorting_indices]
        self.concepts_similarity = concepts_similarity[sorting_indices]

        self.samples: int = samples
        self.max_steps: int = max_steps
        self.step: float = step
        self.change_multiplier: float = change_multiplier

        # We don't have to ask experts for the same concepts
        self._checked_concepts: typing.Set[int] = set()
        # Minimum similarity between vectors is -1
        self._min_threshold: float = -1
        # Maximum similarity between vectors is 1
        self._max_threshold: float = 1

        # Let's start from the highest similarity to ensure minimum amount of steps
        self.threshold_: float = 1

    def _ask_expert(self, available_concepts_indices):
        # Get random concepts (the ones above the threshold)
        concepts_to_show = set(
            np.random.choice(
                available_concepts_indices, len(available_concepts_indices)
            ).tolist()
        )
        # Remove those already presented to an expert
        concepts_to_show = concepts_to_show - self._checked_concepts
        self._checked_concepts.update(concepts_to_show)
        # Print message for an expert and concepts to be classified
        if concepts_to_show:
            print("\nAre those concepts related to medicine?\n")
            print(
                "\n".join(
                    f"{i}. {concept}"
                    for i, concept in enumerate(
                        self.concepts[list(concepts_to_show)[: self.samples]]
                    )
                ),
                "\n",
            )
            return input("[y]es / [n]o / [any]quit ")
        return "y"

    # True - keep asking, False - stop the algorithm
    def _parse_expert_decision(self, decision) -> bool:
        if decision.lower() == "y":
            # You can't go higher as current threshold is related to medicine
            self._max_threshold = self.threshold_
            if self.threshold_ - self.step < self._min_threshold:
                return False
            # Lower the threshold
            self.threshold_ -= self.step
            return True
        if decision.lower() == "n":
            # You can't got lower than this, as current threshold is not related to medicine already
            self._min_threshold = self.threshold_
            # Multiply threshold to pinpoint exact spot
            self.step *= self.change_multiplier
            if self.threshold_ + self.step < self._max_threshold:
                return False
            # Lower the threshold
            self.threshold_ += self.step
            return True
        return False

    def fit(self):
        for _ in range(self.max_steps):
            available_concepts_indices = np.nonzero(
                self.concepts_similarity >= self.threshold_
            )[0]
            if available_concepts_indices.size != 0:
                decision = self._ask_expert(available_concepts_indices)
                if not self._parse_expert_decision(decision):
                    break
            else:
                self.threshold_ -= self.step
        return self

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

Более того, вам не нужно проходить через все сэмплы, только небольшую часть.Вы можете решить, сколько образцов составляют медицинский термин (следует ли считать 40 медицинских образцов и 10 немедицинских образцов медицинскими?), Что позволит вам настроить этот подход в соответствии с вашими предпочтениями.Если есть выброс (скажем, 1 образец из 50 не является медицинским), я бы посчитал, что порог все еще действителен.

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

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

Когда мы получим порог от эксперта, классификация будет мгновенной, вот простой класс для классификации:

class Classifier:
    def __init__(self, centroid, threshold: float):
        self.centroid = centroid
        self.threshold: float = threshold

    def predict(self, concepts_pipe):
        predictions = []
        for concept in concepts_pipe:
            predictions.append(self.centroid.similarity(concept) > self.threshold)
        return predictions

И для краткости, вот окончательный исходный код:

import json
import typing

import numpy as np
import spacy


class Similarity:
    def __init__(self, centroid, nlp, n_threads: int, batch_size: int):
        # In our case it will be medicine
        self.centroid = centroid

        # spaCy's Language model (english), which will be used to return similarity to
        # centroid of each concept
        self.nlp = nlp
        self.n_threads: int = n_threads
        self.batch_size: int = batch_size

        self.missing: typing.List[int] = []

    def __call__(self, concepts):
        concepts_similarity = []
        # nlp.pipe is faster for many documents and can work in parallel (not blocked by GIL)
        for i, concept in enumerate(
            self.nlp.pipe(
                concepts, n_threads=self.n_threads, batch_size=self.batch_size
            )
        ):
            if concept.has_vector:
                concepts_similarity.append(self.centroid.similarity(concept))
            else:
                # If document has no vector, it's assumed to be totally dissimilar to centroid
                concepts_similarity.append(-1)
                self.missing.append(i)

        return np.array(concepts_similarity)


class ActiveLearner:
    def __init__(
        self,
        concepts,
        concepts_similarity,
        samples: int,
        max_steps: int,
        step: float = 0.05,
        change_multiplier: float = 0.7,
    ):
        sorting_indices = np.argsort(-concepts_similarity)
        self.concepts = concepts[sorting_indices]
        self.concepts_similarity = concepts_similarity[sorting_indices]

        self.samples: int = samples
        self.max_steps: int = max_steps
        self.step: float = step
        self.change_multiplier: float = change_multiplier

        # We don't have to ask experts for the same concepts
        self._checked_concepts: typing.Set[int] = set()
        # Minimum similarity between vectors is -1
        self._min_threshold: float = -1
        # Maximum similarity between vectors is 1
        self._max_threshold: float = 1

        # Let's start from the highest similarity to ensure minimum amount of steps
        self.threshold_: float = 1

    def _ask_expert(self, available_concepts_indices):
        # Get random concepts (the ones above the threshold)
        concepts_to_show = set(
            np.random.choice(
                available_concepts_indices, len(available_concepts_indices)
            ).tolist()
        )
        # Remove those already presented to an expert
        concepts_to_show = concepts_to_show - self._checked_concepts
        self._checked_concepts.update(concepts_to_show)
        # Print message for an expert and concepts to be classified
        if concepts_to_show:
            print("\nAre those concepts related to medicine?\n")
            print(
                "\n".join(
                    f"{i}. {concept}"
                    for i, concept in enumerate(
                        self.concepts[list(concepts_to_show)[: self.samples]]
                    )
                ),
                "\n",
            )
            return input("[y]es / [n]o / [any]quit ")
        return "y"

    # True - keep asking, False - stop the algorithm
    def _parse_expert_decision(self, decision) -> bool:
        if decision.lower() == "y":
            # You can't go higher as current threshold is related to medicine
            self._max_threshold = self.threshold_
            if self.threshold_ - self.step < self._min_threshold:
                return False
            # Lower the threshold
            self.threshold_ -= self.step
            return True
        if decision.lower() == "n":
            # You can't got lower than this, as current threshold is not related to medicine already
            self._min_threshold = self.threshold_
            # Multiply threshold to pinpoint exact spot
            self.step *= self.change_multiplier
            if self.threshold_ + self.step < self._max_threshold:
                return False
            # Lower the threshold
            self.threshold_ += self.step
            return True
        return False

    def fit(self):
        for _ in range(self.max_steps):
            available_concepts_indices = np.nonzero(
                self.concepts_similarity >= self.threshold_
            )[0]
            if available_concepts_indices.size != 0:
                decision = self._ask_expert(available_concepts_indices)
                if not self._parse_expert_decision(decision):
                    break
            else:
                self.threshold_ -= self.step
        return self


class Classifier:
    def __init__(self, centroid, threshold: float):
        self.centroid = centroid
        self.threshold: float = threshold

    def predict(self, concepts_pipe):
        predictions = []
        for concept in concepts_pipe:
            predictions.append(self.centroid.similarity(concept) > self.threshold)
        return predictions


if __name__ == "__main__":
    nlp = spacy.load("en_vectors_web_lg")

    centroid = nlp("medicine")

    concepts = json.load(open("concepts_new.txt"))
    concepts_similarity = Similarity(centroid, nlp, n_threads=-1, batch_size=4096)(
        concepts
    )

    learner = ActiveLearner(
        np.array(concepts), concepts_similarity, samples=20, max_steps=50
    ).fit()
    print(f"Found threshold {learner.threshold_}\n")

    classifier = Classifier(centroid, learner.threshold_)
    pipe = nlp.pipe(concepts, n_threads=-1, batch_size=4096)
    predictions = classifier.predict(pipe)
    print(
        "\n".join(
            f"{concept}: {label}"
            for concept, label in zip(concepts[20:40], predictions[20:40])
        )
    )

После ответа на некоторые вопросы с порогом 0,1 (все, что находится между [-1, 0.1) считается немедицинским, в то время как [0.1, 1] считается медицинским), я получил следующие результаты:

kartagener s syndrome: True
summer season: True
taq: False
atypical neuroleptic: True
anterior cingulate: False
acute respiratory distress syndrome: True
circularity: False
mutase: False
adrenergic blocking drug: True
systematic desensitization: True
the turning point: True
9l: False
pyridazine: False
bisoprolol: False
trq: False
propylhexedrine: False
type 18: True
darpp 32: False
rickettsia conorii: False
sport shoe: True

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

Возможные улучшения

Как уже упоминалось в начале, использование моего подхода в сочетании с другими ответами, вероятно, оставило бы такие идеи, как sport shoeпринадлежность к medicine и подход к активному обучению были бы скорее решающим голосом в случае различий между двумя упомянутыми выше эвристиками.

Мы могли бы также создать ансамбль активного обучения.Вместо одного порога, скажем 0,1, мы бы использовали несколько из них (либо увеличивая, либо уменьшая), скажем, это 0.1, 0.2, 0.3, 0.4, 0.5.

Скажем, sport shoe получает, для каждого порога это соответствует True/Falseкак это:

True True False False False,

Делая большинство голосов, мы отметили бы его non-medical 3 из 2 голосов.Кроме того, слишком строгий порог также уменьшил бы, если бы пороговые значения ниже этого превышали его (если True/False будет выглядеть так: True True True False False).

Окончательное возможное улучшение, которое я придумал: В приведенном выше коде я использую вектор Doc, представляющий собой вектор слов, создающий концепцию.Скажем, пропущено одно слово (векторы, состоящие из нулей), в таком случае оно будет отталкиваться от medicine центроида.Вы можете этого не хотеть (поскольку некоторые нишевые медицинские термины [такие сокращения, как gpv или другие) могут не иметь их представления), в таком случае вы можете усреднить только те векторы, которые отличаются от нуля.

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

0 голосов
/ 19 февраля 2019

Вы можете попытаться классифицировать категории википедии по ссылкам mediawiki и обратным ссылкам, возвращаемым для каждой категории

import re
from mediawiki import MediaWiki

#TermFind will search through a list a given term
def TermFind(term,termList):
    responce=False
    for val in termList:
        if re.match('(.*)'+term+'(.*)',val):
            responce=True
            break
    return responce

#Find if the links and backlinks lists contains a given term 
def BoundedTerm(wikiPage,term):
    aList=wikiPage.links
    bList=wikiPage.backlinks
    responce=False
    if TermFind(term,aList)==True and TermFind(term,bList)==True:
         responce=True
    return responce

container=[]
wikipedia = MediaWiki()
for val in termlist:
    cpage=wikipedia.page(val)
    if BoundedTerm(cpage,'term')==True:
        container.append('medical')
    else:
        container.append('nonmedical')

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

0 голосов
/ 16 февраля 2019

В NLP существует понятие слова Векторы, которое в основном делает это, просматривая огромные объемы текста, пытается преобразовать слова в многомерные векторы, а затем уменьшает расстояние между этими векторами, больше сходства между ними.хорошо, что многие люди уже сгенерировали эти словосочетания и сделали их доступными по очень разрешительным лицензиям, и в вашем случае вы работаете с Википедией, и здесь для них существуют векторные словосочетания http://dumps.wikimedia.org/enwiki/latest/enwiki-latest-pages-articles.xml.bz2

Сейчасони были бы наиболее подходящими для этой задачи, так как они содержат большинство слов из корпуса Википедии, но в случае, если они не подходят для вас или будут удалены в будущем, вы можете использовать одно из них, я перечислю ниже больше из них, с тем, что сказал, есть лучший способ сделать это, то есть, передав их в универсальный языковой модуль tenorflow embed, в котором вам не нужно выполнять большую часть тяжелой работы, вы можете прочитать больше об этом здесь. Причина, по которой я поставил это после ВикипедииExt dump - потому что я слышал, как люди говорят, что с ними трудно работать при работе с медицинскими образцами. В этой статье действительно предлагается решение для решения этой проблемы, но я никогда не пробовал этого, поэтому я не могу быть уверен в его точности.

Теперь, как вы можете использовать вложение слов из тензорного потока, просто, простоdo

embed = hub.Module("https://tfhub.dev/google/universal-sentence-encoder/2")
embeddings = embed(["Input Text here as"," List of strings"])
session.run(embeddings)

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

С учетом сказанного, я бы рекомендовал сначала проверить модуль встраивания tenorlfow и их предварительно обученные встраивания слов, если они не работают для вас, проверьте ссылку на Викимедиа, если это тоже не работает, тогда продолжайтес концепциями бумаги, которую я связал.Поскольку этот ответ описывает подход НЛП, он не будет точным на 100%, поэтому помните об этом, прежде чем продолжить.

Перчаточные векторы https://nlp.stanford.edu/projects/glove/

Быстрый текст Facebook: https://github.com/facebookresearch/fastText/blob/master/pretrained-vectors.md

Или это http://www.statmt.org/lm-benchmark/1-billion-word-language-modeling-benchmark-r13output.tar.gz

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

Редактировать Добавлен код для тем кластера

Вкратце, вместо использования вектора слов, я кодирую их суммарные предложения

file content.py

def AllTopics():
    topics = []# list all your topics, not added here for space restricitons
    for i in range(len(topics)-1):
        yield topics[i]

File summaryGenerator.py

import wikipedia
import pickle
from content import Alltopics
summary = []
failed = []
for topic in Alltopics():
    try:
        summary.append(wikipedia.summary(tuple((topic,str(topic)))))
    except Exception as e:
        failed.append(tuple((topic,e)))
with open("summary.txt", "wb") as fp:
    pickle.dump(summary , fp)
with open('failed.txt', 'wb') as fp:
    pickle.dump('failed', fp)

Файл SimilartiyCalculator.py

import tensorflow as tf
import tensorflow_hub as hub
import numpy as np
import os
import pandas as pd
import re
import pickle
import sys
from sklearn.cluster import AgglomerativeClustering
from sklearn import metrics
from scipy.cluster import hierarchy
from scipy.spatial import distance_matrix


try:
    with open("summary.txt", "rb") as fp:   # Unpickling
        summary = pickle.load(fp)
except Exception as e:
    print ('Cannot load the summary file, Please make sure that it exists, if not run Summary Generator first', e)
    sys.exit('Read the error message')

module_url = "https://tfhub.dev/google/universal-sentence-encoder-large/3"
embed = hub.Module(module_url)

tf.logging.set_verbosity(tf.logging.ERROR)
messages = [x[1] for x in summary]
labels = [x[0] for x in summary]
with tf.Session() as session:
    session.run([tf.global_variables_initializer(), tf.tables_initializer()])
    message_embeddings = session.run(embed(messages)) # In message embeddings each vector is a second (1,512 vector) and is numpy.ndarray (noOfElemnts, 512)

X = message_embeddings
agl = AgglomerativeClustering(n_clusters=5, affinity='euclidean', memory=None, connectivity=None, compute_full_tree='auto', linkage='ward', pooling_func='deprecated')
agl.fit(X)
dist_matrix = distance_matrix(X,X)
Z = hierarchy.linkage(dist_matrix, 'complete')
dendro = hierarchy.dendrogram(Z)
cluster_labels = agl.labels_

Он также размещен на GitHub на https://github.com/anandvsingh/WikipediaSimilarity Где вы можете найти файл similarity.txt и другие файлы. В моем случае я не смог запустить его по всем темам, но я призываю вас запустить его по полному списку тем (напрямую клонироватьхранилище и запустите SummaryGenerator.py) и загрузите аналогarity.txt по запросу, если вы не получили ожидаемый результат.И, если возможно, также загрузите message_embeddings в CSV-файл как темы и вложения.

Изменения после редактирования 2 Переключение SimilarityGenerator на кластеризацию на основе иерархии (агломеративную) Я бы предложил вамчтобы сохранить названия заголовков внизу дендрограммы, и для этого посмотрите определение дендрограммы здесь , я проверил просмотр некоторых образцов, и результаты выглядят довольно неплохо, вы можете изменить значение n_clusters на отличноНастройте свою модель.Примечание. Для этого необходимо снова запустить генератор итогов.Я думаю, что вы должны быть в состоянии взять это отсюда, что вам нужно сделать, это попробовать несколько значений n_cluster и посмотреть, в котором все медицинские термины сгруппированы вместе, а затем найти cluster_label для этого кластера, и все готово,Так как здесь мы группируем по сводке, кластеры будут более точными.Если у вас возникли какие-либо проблемы или вы чего-то не поняли, прокомментируйте ниже.

0 голосов
/ 16 февраля 2019

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

https://www.analyticsvidhya.com/blog/2018/10/stepwise-guide-topic-modeling-latent-semantic-analysis/

0 голосов
/ 11 февраля 2019

"Поэтому я хотел бы знать, есть ли способ получить parent category категорий (например, категории enzyme inhibitor и bypass surgery относятся к medical родительской категории)"

Категории MediaWiki сами по себе являются страницами вики.«Родительская категория» - это просто категория, к которой относится страница «дочерней» категории.Таким образом, вы можете получить родительские категории категории точно так же, как и категории любой другой вики-страницы.

Например, используя pymediawiki :

p = wikipedia.page('Category:Enzyme inhibitors')
parents = p.categories
...