mirror of
https://github.com/storytold/artcraft.git
synced 2026-11-04 09:28:11 +00:00
34 KiB
34 KiB
In [25]:
"""
You can run either this notebook locally (if you have all the dependencies and a GPU) or on Google Colab.
Instructions for setting up Colab are as follows:
1. Open a new Python 3 notebook.
2. Import this notebook from GitHub (File -> Upload Notebook -> "GITHUB" tab -> copy/paste GitHub URL)
3. Connect to an instance with a GPU (Runtime -> Change runtime type -> select "GPU" for hardware accelerator)
4. Run this cell to set up dependencies.
"""
## Install dependencies
!pip install wget
!pip install faiss-gpu
## Install NeMo
BRANCH = 'v1.0.2'
!python -m pip install git+https://github.com/NVIDIA/NeMo.git@$BRANCH#egg=nemo_toolkit[all]In [6]:
import faiss
import torch
import wget
import os
import numpy as np
import pandas as pd
from omegaconf import OmegaConf
from pytorch_lightning import Trainer
from IPython.display import display
from tqdm import tqdm
from nemo.collections import nlp as nemo_nlp
from nemo.utils.exp_manager import exp_managerIn [26]:
# Download data into project directory
PROJECT_DIR = "." #Change if you don't want the current directory to be the project dir
DATA_DIR = os.path.join(PROJECT_DIR, "tiny_example_data")
if not os.path.isdir(os.path.join(DATA_DIR)):
wget.download('https://dldata-public.s3.us-east-2.amazonaws.com/tiny_example_data.zip',
os.path.join(PROJECT_DIR, "tiny_example_data.zip"))
!unzip {PROJECT_DIR}/tiny_example_data.zip -d {PROJECT_DIR}In [9]:
raw_data = pd.read_csv(os.path.join(DATA_DIR, "tiny_example_dev_data.csv"), names=["ID", "CONCEPT"], index_col=False)
print(raw_data)ID CONCEPT 0 1 Head ache 1 1 Headache 2 1 Migraine 3 1 Pain in the head 4 1 cephalgia 5 1 cephalalgia 6 2 heart attack 7 2 Myocardial infraction 8 2 necrosis of heart muscle 9 2 MI 10 3 CAD 11 3 Coronary artery disease 12 3 atherosclerotic heart disease 13 3 heart disease 14 3 damage of major heart blood vessels 15 4 myocardial ischemia 16 4 cardiac ischemia 17 4 reduced ability to pump blood 18 5 gradual loss of kidney function 19 5 kidneys cannot filter blood 20 5 chronic kidney disease 21 5 chronic kidney failure 22 5 CKD 23 6 alchohol intoxication 24 6 acute alchohol intoxication 25 6 alchohol poisoning 26 6 severe drunkenness 27 6 over consumption of alcohol 28 7 diabetes mellitus 29 7 diabetes 30 7 inability to process glucose 31 7 unable to take up sugar 32 7 Type 2 diabetes 33 8 Hyperinsulinemia 34 8 High blood sugar 35 8 abnormally high levels of insulin 36 9 Dipeptidyl peptidase-4 inhibitor 37 9 dpp-4 inhibitor 38 9 alogliptin 39 9 Nesina 40 9 Vipidia 41 10 hypoglycemia 42 10 low blood sugar 43 11 anticoagulants 44 11 blood thinners 45 11 Apixaban 46 11 Eliquis 47 12 Ibuprofen 48 12 Aspirin 49 12 over the counter nonsteroidal anti-inflammator... 50 12 NSAID
In [10]:
training_data = pd.read_table(os.path.join(DATA_DIR, "tiny_example_train_pairs.tsv"), names=["ID", "CONCEPT_SYN1", "CONCEPT_SYN2"], delimiter='\t')
print(training_data.head(10))ID CONCEPT_SYN1 CONCEPT_SYN2 0 1 Pain in the head cephalgia 1 1 Pain in the head cephalalgia 2 1 Migraine cephalgia 3 1 Head ache Pain in the head 4 1 Head ache Migraine 5 1 Head ache cephalalgia 6 1 Headache Migraine 7 1 Migraine cephalalgia 8 1 cephalgia cephalalgia 9 1 Headache Pain in the head
In [11]:
# Download config
wget.download("https://raw.githubusercontent.com/vadam5/NeMo/main/examples/nlp/entity_linking/conf/tiny_example_entity_linking_config.yaml",
os.path.join(PROJECT_DIR, "tiny_example_entity_linking_config.yaml"))
# Load in config file
cfg = OmegaConf.load(os.path.join(PROJECT_DIR, "tiny_example_entity_linking_config.yaml"))
# Set config file variables
cfg.project_dir = PROJECT_DIR
cfg.model.nemo_path = os.path.join(PROJECT_DIR, "tiny_example_sap_bert_model.nemo")
cfg.model.train_ds.data_file = os.path.join(DATA_DIR, "tiny_example_train_pairs.tsv")
cfg.model.validation_ds.data_file = os.path.join(DATA_DIR, "tiny_example_validation_pairs.tsv")In [27]:
# Initialize the trainer and model
trainer = Trainer(**cfg.trainer)
exp_manager(trainer, cfg.get("exp_manager", None))
model = nemo_nlp.models.EntityLinkingModel(cfg=cfg.model, trainer=trainer)In [28]:
# Train and save the model
trainer.fit(model)
model.save_to(cfg.model.nemo_path)In [29]:
device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
# Restore second stage pretrained model
sap_model_cfg = cfg
sap_model_cfg.index.index_save_name = os.path.join(PROJECT_DIR, "tiny_example_entity_linking_index")
sap_model_cfg.index.index_ds.data_file = os.path.join(DATA_DIR, "tiny_example_index_data.tsv")
sap_model = nemo_nlp.models.EntityLinkingModel.restore_from(sap_model_cfg.model.nemo_path).to(device)
# Load original model
base_model_cfg = OmegaConf.load(os.path.join(PROJECT_DIR, "tiny_example_entity_linking_config.yaml"))
# Set train/val datasets to None to avoid loading datasets associated with training
base_model_cfg.model.train_ds = None
base_model_cfg.model.validation_ds = None
base_model_cfg.index.index_save_name = os.path.join(PROJECT_DIR, "base_model_index")
base_model_cfg.index.index_ds.data_file = os.path.join(DATA_DIR, "tiny_example_index_data.tsv")
base_model = nemo_nlp.models.EntityLinkingModel(base_model_cfg.model).to(device)In [15]:
# Helper function to get data embeddings
def get_embeddings(model, dataloader):
embeddings, cids = [], []
with torch.no_grad():
for batch in tqdm(dataloader):
input_ids, token_type_ids, attention_mask, batch_cids = batch
batch_embeddings = model.forward(input_ids=input_ids.to(device),
token_type_ids=token_type_ids.to(device),
attention_mask=attention_mask.to(device))
# Accumulate index embeddings and their corresponding IDs
embeddings.extend(batch_embeddings.cpu().detach().numpy())
cids.extend(batch_cids)
return embeddings, cidsIn [16]:
def evaluate(model, test_kb, test_queries, ks):
# Initialize knowledge base and query data loaders
test_kb_dataloader = model.setup_dataloader(test_kb, is_index_data=True)
test_query_dataloader = model.setup_dataloader(test_queries, is_index_data=True)
# Get knowledge base and query embeddings
test_kb_embs, test_kb_cids = get_embeddings(model, test_kb_dataloader)
test_query_embs, test_query_cids = get_embeddings(model, test_query_dataloader)
# Calculate the cosine distance between each query and knowledge base concept
score_matrix = np.matmul(np.array(test_query_embs), np.array(test_kb_embs).T)
accs = {k : 0 for k in ks}
# Compare the knowledge base IDs of the knowledge base entities with
# the smallest cosine distance from the query
for query_idx in tqdm(range(len(test_query_cids))):
query_emb = test_query_embs[query_idx]
query_cid = test_query_cids[query_idx]
query_scores = score_matrix[query_idx]
for k in ks:
topk_idxs = np.argpartition(query_scores, -k)[-k:]
topk_cids = [test_kb_cids[idx] for idx in topk_idxs]
# If the correct query ID is amoung the top k closest kb IDs
# the model correctly linked the entity
match = int(query_cid in topk_cids)
accs[k] += match
for k in ks:
accs[k] /= len(test_query_cids)
return accsIn [30]:
# Create configs for our test data
test_kb = OmegaConf.create({
"data_file": os.path.join(DATA_DIR, "tiny_example_test_kb.tsv"),
"max_seq_length": 128,
"batch_size": 10,
"shuffle": False,
})
test_queries = OmegaConf.create({
"data_file": os.path.join(DATA_DIR, "tiny_example_test_queries.tsv"),
"max_seq_length": 128,
"batch_size": 10,
"shuffle": False,
})
ks = [1, 5]
# Evaluate both models on our test data
base_accs = evaluate(base_model, test_kb, test_queries, ks)
base_accs["Model"] = "BERT Base Baseline"
sap_accs = evaluate(sap_model, test_kb, test_queries, ks)
sap_accs["Model"] = "BERT + SAP"
print("Top 1 and Top 5 Accuracy Comparison:")
results_df = pd.DataFrame([base_accs, sap_accs], columns=["Model", 1, 5])
results_df = results_df.style.set_properties(**{'text-align': 'left', }).set_table_styles([dict(selector='th', props=[('text-align', 'left')])])
display(results_df)In [18]:
def build_index(cfg, model):
# Setup index dataset loader
index_dataloader = model.setup_dataloader(cfg.index.index_ds, is_index_data=True)
# Get index dataset embeddings
embeddings, _ = get_embeddings(model, index_dataloader)
# Train IVFFlat index using faiss
embeddings = np.array(embeddings)
quantizer = faiss.IndexFlatL2(cfg.index.dims)
index = faiss.IndexIVFFlat(quantizer, cfg.index.dims, cfg.index.nlist)
index = faiss.index_cpu_to_all_gpus(index)
index.train(embeddings)
# Add concept embeddings to index
for i in tqdm(range(0, embeddings.shape[0], cfg.index.index_batch_size)):
index.add(embeddings[i:i+cfg.index.index_batch_size])
# Save index
faiss.write_index(faiss.index_gpu_to_cpu(index), cfg.index.index_save_name)In [31]:
build_index(sap_model_cfg, sap_model.to(device))
build_index(base_model_cfg, base_model.to(device))In [20]:
def query_index(cfg, model, index, queries, id2string):
# Get query embeddings from our entity linking encoder model
query_embs = get_query_embedding(queries, model).cpu().detach().numpy()
# Use query embedding to find closest concept embedding in knowledge base
distances, neighbors = index.search(query_embs, cfg.index.top_n)
# Get the canonical strings corresponding to the IDs of the query's nearest neighbors in the kb
neighbor_concepts = [[id2string[concept_id] for concept_id in query_neighbor] \
for query_neighbor in neighbors]
# Display most similar concepts in the knowledge base.
for query_idx in range(len(queries)):
print(f"\nThe most similar concepts to {queries[query_idx]} are:")
for cid, concept, dist in zip(neighbors[query_idx], neighbor_concepts[query_idx], distances[query_idx]):
print(cid, concept, 1 - dist)
def get_query_embedding(queries, model):
# Tokenize our queries
model_input = model.tokenizer(queries,
add_special_tokens = True,
padding = True,
truncation = True,
max_length = 512,
return_token_type_ids = True,
return_attention_mask = True)
# Pass tokenized input into model
query_emb = model.forward(input_ids=torch.LongTensor(model_input["input_ids"]).to(device),
token_type_ids=torch.LongTensor(model_input["token_type_ids"]).to(device),
attention_mask=torch.LongTensor(model_input["attention_mask"]).to(device))
return query_embIn [21]:
# Load indices
sap_index = faiss.read_index(sap_model_cfg.index.index_save_name)
base_index = faiss.read_index(base_model_cfg.index.index_save_name)In [22]:
# Map concept IDs to one canonical string
index_data = open(sap_model_cfg.index.index_ds.data_file, "r", encoding='utf-8-sig')
id2string = {}
for line in index_data:
cid, concept = line.split("\t")
id2string[int(cid) - 1] = concept.strip()In [23]:
id2stringOut [23]:
{0: 'Headache',
1: 'Myocardial infraction',
2: 'Coronary artery disease',
3: 'myocardial ischemia',
4: 'chronic kidney disease',
5: 'alchohol intoxication',
6: 'diabetes',
7: 'Hyperinsulinemia',
8: 'Nesina',
9: 'hypoglycemia',
10: 'anticoagulants',
11: 'Ibuprofen'}In [24]:
# Some sample queries
queries = ["high blood sugar", "head pain"]
# Query BERT Base
print("BERT Base output before Self Alignment Pretraining:")
query_index(base_model_cfg, base_model, base_index, queries, id2string)
print("\n" + "-" * 50 + "\n")
# Query SAP BERT
print("SAP BERT output after Self Alignment Pretraining:")
query_index(sap_model_cfg, sap_model, sap_index, queries, id2string)
print("\n" + "-" * 50 + "\n")BERT Base output before Self Alignment Pretraining: The most similar concepts to high blood sugar are: 6 diabetes 0.9095035567879677 0 Headache 0.9046077281236649 8 Nesina 0.8512845635414124 The most similar concepts to head pain are: 1 Myocardial infraction 0.7848672568798065 4 chronic kidney disease 0.7667323648929596 3 myocardial ischemia 0.761662557721138 -------------------------------------------------- SAP BERT output after Self Alignment Pretraining: The most similar concepts to high blood sugar are: 7 Hyperinsulinemia 0.28035879135131836 3 myocardial ischemia 0.054734230041503906 6 diabetes 0.053402602672576904 The most similar concepts to head pain are: 0 Headache 0.6553303301334381 6 diabetes 0.19157332181930542 2 Coronary artery disease 0.12962108850479126 --------------------------------------------------