Как сгруппировать категории википедии в Python?

Для каждой концепции моего набора данных я сохранил соответствующие категории википедии. Например, рассмотрим следующие 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. Тем не менее, я не ограничен только этими двумя библиотеками и рад иметь решения, использующие и другие библиотеки.

РЕДАКТИРОВАТЬ

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

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

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

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

  1. Использование библиотеки pymediawiki

    импортировать 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% (если предложенный алгоритм способен обнаружить многие медицинские концепции, которых мне достаточно)

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

Ответы

Ответ 1

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

        # spaCy 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 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 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, который, по мнению эксперта, находит оптимальный порог 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 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 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 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 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 тяжести medicine. Вы можете этого не хотеть (поскольку некоторые нишевые медицинские термины [аббревиатуры, такие как gpv или другие) могут не иметь их представления), в таком случае вы можете усреднить только те векторы, которые отличаются от нуля.

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

Ответ 2

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

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

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

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

Ответ 3

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

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

Теперь, как вы можете использовать вложение слова из tenorflow, просто, просто сделайте

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, добавьте свою проблему к вопросу и комментарию ниже, оттуда мы можем продолжить.

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

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

файл 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]

Файл 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/WikipediaSdentifity, где вы можете найти файл similarity.txt и другие файлы. В моем случае я не смог запустить его по всем темам, но настоятельно рекомендую запустите его по полному списку тем (напрямую клонируйте репозиторий и запустите SummaryGenerator.py) и загрузите файл Similarity.txt с помощью запроса извлечения, если вы не получили ожидаемого результата. И, если возможно, также загрузите message_embeddings в CSV файл как темы и там вложения.

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

Ответ 4

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

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, чтобы сделать классификацию, или один вызов для нескольких терминов и объединить результат для классификации. Надеюсь, поможет

Ответ 5

Библиотека 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% от того, что должно быть в списке, по крайней мере, насколько мне известно.

Ответ 6

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

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