mirror of
https://github.com/storytold/artcraft.git
synced 2026-11-04 09:28:11 +00:00
4.3 MiB
4.3 MiB
In [ ]:
"""
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.
"""
# If you're using Google Colab and not running locally, run this cell
# install NeMo
BRANCH = 'v1.0.2'
!python -m pip install git+https://github.com/NVIDIA/NeMo.git@$BRANCH#egg=nemo_toolkit[nlp]In [ ]:
# If you're not using Colab, you might need to upgrade jupyter notebook to avoid the following error:
# 'ImportError: IProgress not found. Please update jupyter and ipywidgets.'
! pip install ipywidgets
! jupyter nbextension enable --py widgetsnbextension
# Please restart the kernel after running this cellIn [ ]:
import os
import wget
import torch
import pytorch_lightning as pl
from omegaconf import OmegaConf
from nemo.collections import nlp as nemo_nlp
from nemo.utils.exp_manager import exp_manager
from nemo.collections.nlp.models import NeuralMachineTranslationModelIn [ ]:
# set the following paths
DATA_DIR = "PATH_TO_DATA"
WORK_DIR = "PATH_TO_CHECKPOINTS_AND_LOGS"
# NeMo Version
BRANCH = 'v1.0.2'
In [ ]:
# download import_datasets.py script to download and preprocess Text2Sparql
os.makedirs(WORK_DIR, exist_ok=True)
if not os.path.exists(WORK_DIR + "/get_squad.py"):
print("Downloading import_datasets.py...")
wget.download(f"https://raw.githubusercontent.com/NVIDIA/NeMo/{BRANCH}/examples/nlp/text2sparql/data/import_datasets.py", WORK_DIR)
else:
print("import_datasets.py already exists")In [ ]:
# run script
! python $WORK_DIR/import_datasets.py --source_data_dir $DATA_DIR --target_data_dir $DATA_DIRIn [ ]:
# This is the model configuration file that we will download, do not change this
MODEL_CONFIG = "text2sparql_config.yaml"
# training parameters
GPUS = 1 if torch.cuda.is_available() else 0 # 0 for CPU, or list of GPU indicies
MAX_EPOCHS = 2 # number of training epochs
# model parameters, play with these
NEMO_PATH = f"{WORK_DIR}/bart.nemo" # specify model savepath (models are saved as .nemo files)
BATCH_SIZE = 16
MAX_SEQ_LENGTH = 150
# specify seq2seq model you want to use
PRETRAINED_BART_MODEL = "facebook/bart-base"
TOKENIZER_NAME = "facebook/bart-base"
TRAIN_FILE = f"{DATA_DIR}/train.tsv"
EVAL_FILE = f"{DATA_DIR}/test_easy.tsv"
TEST_FILE = f"{DATA_DIR}/test_easy.tsv" # can change to test_hard.tsv
# training parameters
LEARNING_RATE = 0.00004In [ ]:
# download the model's default configuration file
config_dir = WORK_DIR + "/configs/"
os.makedirs(config_dir, exist_ok=True)
if not os.path.exists(config_dir + MODEL_CONFIG):
print("Downloading config file...")
wget.download(f"https://raw.githubusercontent.com/NVIDIA/NeMo/{BRANCH}/examples/nlp/text2sparql/conf/{MODEL_CONFIG}", config_dir)
else:
print("config file is already exists")In [ ]:
# this line will print the entire default config of the model
config_path = f"{WORK_DIR}/configs/{MODEL_CONFIG}"
print(config_path)
config = OmegaConf.load(config_path)
print(OmegaConf.to_yaml(config))In [ ]:
config.trainer.gpus = GPUS
config.trainer.max_epochs = MAX_EPOCHS
config.model.nemo_path = NEMO_PATH
config.model.batch_size = BATCH_SIZE
config.model.max_seq_length = MAX_SEQ_LENGTH
config.model.language_model.pretrained_model_name = PRETRAINED_BART_MODEL
config.model.encoder_tokenizer.tokenizer_name = TOKENIZER_NAME
config.model.decoder_tokenizer.tokenizer_name = TOKENIZER_NAME
config.model.train_ds.filepath = TRAIN_FILE
config.model.validation_ds.filepath = EVAL_FILE
config.model.test_ds.filepath = TEST_FILE
config.model.optim.lr = LEARNING_RATE
config.exp_manager.exp_dir = WORK_DIRIn [ ]:
trainer = pl.Trainer(**config.trainer)In [ ]:
exp_dir = exp_manager(trainer, config.get("exp_manager", None))In [ ]:
nmt_model = NeuralMachineTranslationModel(config.model, trainer=trainer)In [ ]:
# load the TensorBoard notebook extension
try:
from google import colab
COLAB_ENV = True
except (ImportError, ModuleNotFoundError):
COLAB_ENV = False
# Load the TensorBoard notebook extension
if COLAB_ENV:
%load_ext tensorboard
%tensorboard --logdir "{exp_dir}"
else:
print("To use tensorboard, please use this notebook in a Google Colab environment.")In [ ]:
# start model training
trainer.fit(nmt_model)In [ ]:
nmt_model.save_to(config.model.nemo_path)In [ ]:
nmt_model = NeuralMachineTranslationModel.restore_from(restore_path=config.model.nemo_path)In [ ]:
nmt_model.setup_test_data(config.model.validation_ds)In [ ]:
trainer = pl.Trainer(gpus=config.trainer.gpus)
results = trainer.test(nmt_model)In [ ]:
# save results
with open(config.model.test_ds.filepath, "r") as f:
lines = f.readlines()
lines[0] = lines[0].strip() + f"\tpredictions\n"
for i, res in enumerate(results[0]["texts"]):
lines[i + 1] = lines[i + 1].strip() + f"\t{res}\n"
savepath = os.path.join(config.exp_manager.exp_dir, os.path.basename(config.model.test_ds.filepath))
with open(savepath, "w") as f:
f.writelines(lines)
print(f"Predictions saved to {savepath}")In [ ]:
! pip install rapidfuzz
# define helper methods for entity resolution
# matching of entities to q-values is easily performed using levenshtein distance
import os
import re
import json
from typing import Dict, Tuple
from urllib.request import Request, urlopen
from rapidfuzz import fuzz, process
from glob import glob
from nemo.collections.nlp.data.data_utils.data_preprocessing import if_exist
base_url = "https://m.meetkai.com/public_datasets/knowledge/"
prefixes = {
"television_series-5k-preprocessed.json",
"person-5k-preprocessed.json",
"movie-5k-preprocessed.json",
"literary_work-5k-preprocessed.json",
"chemical-5k-preprocessed.json",
}
def download_entities(infold: str):
"""Downloads text2sparql entity files
Args:
infold: save directory path
"""
os.makedirs(infold, exist_ok=True)
for prefix in prefixes:
url = base_url + prefix
print(f"Downloading: {url}")
if if_exist(infold, [prefix]):
print("** Download file already exists, skipping download")
else:
req = Request(url, headers={"User-Agent": "Mozilla/5.0"})
with open(os.path.join(infold, prefix), "wb") as handle:
handle.write(urlopen(req, timeout=20).read())
def url_to_qvalue(url: str) -> str:
"""Get q value from Wikidata url
http://www.wikidata.org/entity/Q494 -> Q494
"""
return url.split("/")[-1]
def load_entities(data_dir: str) -> Dict:
assert os.path.isdir(data_dir), f"{data_dir} is not a valid directory."
fps = glob(os.path.join(data_dir, "*-5k-preprocessed.json"))
data = []
for fp in fps:
with open(fp, "r") as f:
file_data = json.load(f)
data.extend(file_data)
assert data, f"No data was found, please check {data_dir}."
# expand data
expanded_data = dict()
for item in data:
qvalue = url_to_qvalue(item["thing"])
for label in item["labels"]:
expanded_data[label] = qvalue
return expanded_data
class EntityResolver:
def __init__(self, data_dir: str, score_cutoff: float = 80.0):
self.entity_dict = load_entities(data_dir)
self.choices = list(self.entity_dict.keys())
self.score_cutoff = score_cutoff
def resolve_entity(self, entity: str) -> Tuple[str, str, float]:
"""Finds the fuzzy entity match above the cutoff score and returns the Q-value for it.
Returns:
(q-value, top_entity_match, simple_levenshtein_score)
"""
top = process.extractOne(entity, self.choices, scorer=fuzz.ratio, score_cutoff=self.score_cutoff)
if not top:
raise ValueError(f"For entity [{entity}], no valid match above cutoff found.")
return self.lookup(top[0]), top[0], top[1]
def lookup(self, entity: str):
return self.entity_dict[entity]
def resolve(self, text: str) -> str:
"""Replaces all entity matches within a predicted query
assuming entities are always formatted between two brackets.
"""
matches = re.findall(r"\[(.*?)\]", text)
for entity in matches:
try:
q_value = self.resolve_entity(entity)[0]
text = text.replace(f"[{entity}]", f"wd:{q_value}")
except ValueError as e:
print(f"WARNING: {e}")
return textIn [ ]:
# Download entity data
download_entities(DATA_DIR)
# Load the data to the entity resolver
resolver = EntityResolver(data_dir=DATA_DIR)In [ ]:
# Let's use a cherry picked example prediction as not all queries will (and should) return results
example_prediction = "SELECT ?end WHERE { [1,1,1-trifluoro-2-chloro-2-bromoethane] wdt:P31 ?end . }"
example_query = resolver.resolve(example_prediction)
example_queryIn [ ]:
# And query WikiData
import requests
url = "https://query.wikidata.org/sparql"
response = requests.get(url, params = {"format": "json", "query": example_query})
data = response.json()
data