{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "# Extracting Ancient Placenames using Named Entity Recognition and `spaCy`\n", "\n", "In this notebook, we are going to explore an important subfield of natural language processing, named entity recognition or NER. This notebook was inspired by William Mattingly's textbook on the subject, which you can find here: https://ner.pythonhumanities.com/intro.html. I encourage you to browse this book and play around with the code that he provides.\n", "\n", "Objectives:\n", "* Understand what NER is and why it matters\n", "* Collect a training data for a machine learning NER approach\n", "* Train a NER model using `spaCy`'s utilities\n", "* Do some basic inference to see where our model succeeds and where it fails" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "What is NER and why does it matter?\n", "\n", "Named entity recognition describes any method which uses computational methods to extract from unstructured text names of people, places or things. It is a hard classification task, meaning that every word in a document is either a type of named entity or it is not. For example in the following sentences:\n", "> My name is Peter Nadel. I work at Tufts University.\n", "\n", "the token 'Peter Nadel' could be tagged as a PERSON tag, where as Tufts Univerisity could be tagged with a PLACE tag. Importantly, in NER, no token can receive more than one tag.\n", "\n", "As a result, NER can be using in a wide variety of fields and applications." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# The example we'll be looking at today comes from these maps that I made\n", "from IPython.display import IFrame\n", "\n", "IFrame(\n", " \"https://tuftsgis.maps.arcgis.com/apps/webappviewer/index.html?id=576ff8f0e3954ad781916e94dfb34f7e\",\n", " width=1400,\n", " height=800,\n", ")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "To make a map like the one we see above, I needed to extract place names from an ancient text. Thankfully, ToposText (https://topostext.org/) has marked all of the place names already. So why do we need NER?\n", "\n", "ToposText has a lot of texts, but it doesn't have every ancient text. I work quite frequently with faculty members from Classic Studies, examining texts much more obscure than those in ToposText, which may not be digitized at all, let alone have place names marked. So, what we need is an ancient place name NER model, which we can pass any amount of texts and get back all of the places in that text.\n", "\n", "*A quick note on `spaCy`'s native English NER*: `spaCy` has NER built into their English and other models. This NER is of good quality for modern texts, but is quite poor when applied to texts that aren't that similar to the training data (in the English model's case: modern news articles scraped from the web). For specialty tasks like the one we will do today, training your own model will always return better results." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "# Scraping\n", "\n", "Here, I will show how we got our training data and what form it has to be in for training NER models with `spaCy`." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "from bs4 import BeautifulSoup\n", "from tqdm import tqdm\n", "import re\n", "import requests" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "res_html = requests.get(\n", " \"https://topostext.org/work/148\"\n", ").text # The Natural Histories, Plinius Senior" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "topos_test = BeautifulSoup(res_html, features=\"html\")" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "p_tags = topos_test.find_all(\"p\")[2:]\n", "p_tags[3].a # first a tag in the third p tag" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "`spaCy` expects the training data to be in `jsonl` format, meaning that there must be a list where each element is a list that contains the text and a dictionary of the entities that are in the text, with their label (in this case, their HTML class)." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "import collections\n", "import string\n", "\n", "TRAIN_DATA = []\n", "\n", "for i, p in enumerate(p_tags):\n", " document = []\n", " p_text = re.sub(\"(§\\s\\d\\.\\d+)|(§\\s\\d+)\", \"\", p.text)\n", " document.append(p_text.replace(\"\\xa0\\xa0\", \"\"))\n", "\n", " ent_dict = collections.defaultdict(list)\n", " for a in p.find_all(\"a\"):\n", " span_list = []\n", " if a.text.istitle():\n", " if \"href\" in a.attrs:\n", " span_list.append(\n", " a.text.translate(str.maketrans(\"\", \"\", string.punctuation))\n", " )\n", " span_list.append(a[\"href\"].split(\"/\")[1])\n", " else:\n", " span_list.append(\n", " a.text.translate(str.maketrans(\"\", \"\", string.punctuation))\n", " )\n", " span_list.append(a[\"class\"][0])\n", " if len(span_list) > 0:\n", " ent_dict[\"entities\"].append(span_list)\n", " document.append(dict(ent_dict))\n", " TRAIN_DATA.append(document)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "TRAIN_DATA[0]" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Importantly though, you can't just use the text of the entities. It must be the indices of the names within the text. So we can do that below." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## TASK 1\n", "We can't feed our model the raw text of a name. Instead, we need to give it the indices of the name in the original text.\n", "\n", "Let's write some code to do that." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "test = TRAIN_DATA[0]\n", "test" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "type(test)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "test[0], type(test[0])" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "test[0].find(\"Muses\")" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "test[1], type(test[1])" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "test[1][\"entities\"]" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "test[1][\"entities\"], type(test[1][\"entities\"])" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "test[1][\"entities\"][0], type(test[1][\"entities\"][0])" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "test[1][\"entities\"][0][0], type(test[1][\"entities\"][0][0])" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "My Solution" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "test[0].find(\"Muses\")" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "test[1][\"entities\"][0][0]" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "start = test[0].find(test[1][\"entities\"][0][0])" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Answer\n", "`TRAIN_DATA` -> `List[String, Dict[List[String, String]]]`" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "test[0].find(\"Muses\")" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "test[1][\"entities\"]" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# using find method\n", "test[0].find(test[1][\"entities\"][0][0])" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# add length of the original string\n", "start = test[0].find(test[1][\"entities\"][0][0])\n", "end = start + len(test[1][\"entities\"][0][0])\n", "start, end" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# check\n", "test[0][396:401] # looks good!" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# turn this into a function we can use for each string\n", "def names2indices(train_data_instance):\n", " for i, name in enumerate(train_data_instance[1][\"entities\"]):\n", " # get indices\n", " start = train_data_instance[0].find(name[0])\n", " if start != -1:\n", " end = start + len(name[0])\n", " else:\n", " end = -1\n", " pass\n", "\n", " # update dict\n", " train_data_instance[1][\"entities\"][i].pop(0) # remove string name\n", " train_data_instance[1][\"entities\"][i].insert(0, end) # add end\n", " train_data_instance[1][\"entities\"][i].insert(0, start) # add start" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# do it to all of them in our training data\n", "for instance in TRAIN_DATA:\n", " if \"entities\" in instance[1].keys():\n", " names2indices(instance)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "TRAIN_DATA[0]" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Completing our training data\n", "\n", "Now that we can do this to one ToposText URL we can do it to all the others." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# can make this into a single function\n", "def create_TRAIN_DATA(url):\n", " \"\"\"\n", " Takes in a ToposText URL and creates spaCy training data.\n", " \"\"\"\n", "\n", " # scraping\n", " res = requests.get(url)\n", " if res:\n", " res_html = res.text\n", "\n", " soup = BeautifulSoup(res_html, features=\"html\")\n", " TRAIN_DATA = []\n", "\n", " p_tags = soup.find_all(\"p\")[1:]\n", " for i, p in enumerate(p_tags):\n", " document = []\n", " p_text = re.sub(\"(§\\s\\d\\.\\d+)|(§\\s\\d+)\", \"\", p.text)\n", " document.append(p_text.replace(\"\\xa0\\xa0\", \"\"))\n", "\n", " ent_dict = collections.defaultdict(list)\n", " for a in p.find_all(\"a\"):\n", " span_list = []\n", " if a.text.istitle():\n", " if \"href\" in a.attrs:\n", " span_list.append(a.text)\n", " span_list.append(a[\"href\"].split(\"/\")[1])\n", " else:\n", " try:\n", " span_list.append(a.text)\n", " span_list.append(a[\"class\"][0])\n", " except:\n", " continue\n", " if len(span_list) > 0:\n", " ent_dict[\"entities\"].append(span_list)\n", " document.append(dict(ent_dict))\n", " TRAIN_DATA.append(document)\n", "\n", " # reformatting\n", " for instance in TRAIN_DATA:\n", " if \"entities\" in instance[1].keys():\n", " names2indices(instance)\n", "\n", " return TRAIN_DATA" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "create_TRAIN_DATA(\"https://topostext.org/work/148\")" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "TRAIN_DATA = []\n", "# Max of 901\n", "for i in tqdm(range(2, 901)):\n", " train = create_TRAIN_DATA(f\"https://topostext.org/work/{i}\")\n", " for item in train:\n", " TRAIN_DATA.append(item)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "len(TRAIN_DATA)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "TRAIN_DATA[:3]" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# pickling let's us save this work so we don't have to do it again\n", "import pickle\n", "\n", "file_name = \"topostext_training_data.pkl\"\n", "\n", "# pickle and save the data\n", "with open(file_name, \"wb\") as file:\n", " pickle.dump(TRAIN_DATA, file)\n", "\n", "print(f\"Training data saved to {file_name}\")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "# Training\n", "Now that we have our data in the correct form, we can start training.\n", "\n", "First, we have to convert the `TRAIN_DATA` object into a special `spaCy` type called a `DocBin` (code taken from Mattingly)." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "!wget https://tufts.box.com/shared/static/2p4ez2vas90v2zn8mf3lgdv6lltzl75d.pkl -O topostext_training_data.pkl" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "import pickle\n", "\n", "with open(\"/content/topostext_training_data.pkl\", \"rb\") as file:\n", " loaded_training_data = pickle.load(file)\n", "\n", "print(\"Loaded training data length:\", len(loaded_training_data))" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "loaded_training_data[0]" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "The above section in the original Greek.\n", "\n", "![Screenshot 2023-10-16 at 10.00.16 AM.png](data:image/png;base64,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UJj8JB7c8moFAwUDBQOFyRcaKBgoGCgYmMQYKEx+DAZ30aJFgf/AVVddFR588MExaHV8m3jooYfCnDlzxrcTY9z6I488Em6++eYxbrU0VzDQGQOTkskvXrw4XHHFFWHevHn29k8++WRYsGBBZ0yM4tNPfOIT4fWvf33461//GtZbb71w6KGH9q218847L+y5557hox/9aDj88MPDX/7ylxHX/bvf/S4ceOCB4T/+4z/CCSecYP3utdIvfelL9q533313r0VHJf///M//hB122CHccccdw6r/t7/9bbjuuuus7JVXXmmLdV4R+F977bXDXXfdlT+ye46J+v73vx922WWXAE1897vfrRb/2gKjkMji+8QTT4xCzc9U+eMf/zj827/9W7jwwgufSay5+vOf/xwuv/zysGTJEnvK/HBhqCZ7SRouBjigbLLBhz70oajJFldYYYWoCRX322+/+IUvfGHcXlPEG9/0pjfFM844I7761a+Okvj60pezzz47vuIVr4hiKvH++++P73nPe+I//uM/jqju0047LS6//PLxzDPPjFow4kc+8pH4mte8Joo59FTvwoULrS+nnnpqo3Ka8HHTTTeNyyyzDAfmxXXXXXfIf3/29re/Pf7pT39qVC+ZtMjHz33uc5Fy//qv/9q4XJpxww03jO9973stiX6+853vTB/bNX0+9thjh6R7wmc+85m43XbbRe3k4m233RbXWGONuOuuu/rjMfnl/TfYYANr65RTTjFcX3TRRX1t+9vf/naUgBBf9KIXxYcffri27muvvdZo9+Uvf7nR17e+9a24zjrr9ExntZWXxBYMhJa7SXLzla98xd5k9uzZcdttt42S4OIDDzwwbm931FFHxdtvvz1KKo6SAvvWj7333ju++93vruqTtBl32mmn6r7XC5jhP/zDP8R///d/r4pqBxSf9axnxW984xtVWtMLqabid77znabZLd+WW25pjL6uEIv2P//zP9c9apQmKTGuuuqqkQWlV/jDH/4Q77zzTisGg2Y8U9DuMX7yk59Mk4ZcaxcXv/rVr1bpxxxzTIS5jSX8/ve/N4GDNt/1rnfF5z//+XHnnXcelS4gdPzv//5vbd0///nP49VXXx0ff/zx+MUvftHG9Uc/+lFt3pI4Mgw0ZvLz710Yz7n8rkatPfrY4vjzK+bFex96vFH+kml4GPjBD35g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)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "!mkdir data" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "import spacy\n", "from spacy.tokens import DocBin\n", "from pathlib import Path\n", "import warnings\n", "\n", "\n", "def convert(lang: str, TRAIN_DATA, output_path: Path):\n", " nlp = spacy.blank(lang)\n", " nlp.max_length = 2000000\n", " db = DocBin()\n", " for text, annot in TRAIN_DATA:\n", " doc = nlp.make_doc(text)\n", " ents = []\n", " if \"entities\" in annot.keys():\n", " for start, end, label in annot[\"entities\"]:\n", " span = doc.char_span(start, end, label=label)\n", " if span is None:\n", " msg = f\"Skipping entity [{start}, {end}, {label}] in the following text because the character span '{doc.text[start:end]}' does not align with token boundaries:\\n\\n{repr(text)}\\n\"\n", " warnings.warn(msg)\n", " else:\n", " ents.append(span)\n", " try:\n", " doc.ents = ents\n", " except:\n", " continue\n", " db.add(doc)\n", " db.to_disk(output_path)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "from sklearn.model_selection import train_test_split\n", "\n", "# two splits: first split (train and validation for testing our model after it trains)\n", "train_valid, valid_post_training = train_test_split(\n", " loaded_training_data, test_size=0.2, random_state=1\n", ")\n", "\n", "train, valid = train_test_split(train_valid, test_size=0.2, random_state=1)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "len(train), len(valid), len(valid_post_training)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "import warnings\n", "\n", "warnings.filterwarnings(\"ignore\")\n", "\n", "convert(\"en\", train, \"/content/data/train.spacy\")\n", "convert(\"en\", valid, \"/content/data/valid.spacy\")" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "import os\n", "\n", "os.listdir(\"/content/data\")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## TASK 2\n", "\n", "We still need one final piece before we can start training. This is the config file that will tell the training program how to initialize our set up. We can download a blank version here: https://spacy.io/usage/training.\n", "\n", "Finally we just need to add the file locations of our training and validation data. In Colab, double-clicking on the base_config.cfg file will open up an editor on the side. In the train field, put `data/train.spacy` and in the dev field, put `data/valid.spacy`." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "!python -m spacy init fill-config /content/base_config.cfg /content/config.cfg" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# really useful feature in spacy\n", "!python -m spacy debug data /content/config.cfg" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "!python -m spacy train config.cfg --output models" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# zip up model for easier download\n", "!zip -r topostext_ner_model_full.zip models" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "from google.colab import files\n", "\n", "files.download(\"topostext_ner_model_full.zip\")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "# Inference\n", "\n", "Now that we have a model, we can see how well we did. For this inference step, I want to use example texts that I know were not in the training data." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "!git clone https://github.com/pnadelofficial/topos_text_NER.git" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "!unzip topos_text_NER/topostext_ner_model_full.zip" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "inf_text = \"\"\"\n", "An upper vest, once Helen's rich attire,\n", "From Argos by the fam'd adultress brought,\n", "With golden flow'rs and winding foliage wrought,\n", "Her mother Leda's present, when she came\n", "To ruin Troy and set the world on flame;\n", "The scepter Priam's eldest daughter bore,\n", "Her orient necklace, and the crown she wore\n", "Of double texture, glorious to behold,\n", "One order set with gems, and one with gold.\n", "\"\"\"\n", "\n", "# Vergil Aenid 1.643\n", "# perseus.tufts.edu/hopper/text?doc=Perseus%3Atext%3A1999.02.0052%3Abook%3D1%3Acard%3D643" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "import spacy\n", "\n", "trained_nlp = spacy.load(\"models/model-best\")" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "doc = trained_nlp(inf_text)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "type(doc)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "people = []\n", "for ent in doc.ents:\n", " if ent.label_ == \"people\":\n", " people.append(ent)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "people" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "!python -m spacy download en_core_web_sm" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "import spacy\n", "\n", "nlp = spacy.load(\"en_core_web_sm\")" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# spacy pretrained model\n", "test_doc = nlp(inf_text)\n", "[(e, e.label_) for e in test_doc.ents]" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# ours\n", "[(e, e.label_) for e in doc.ents]" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Evaluation\n", "\n", "In this section, we'll use the `valid_post_training` list of examples to calculate an F1 score for our model. This score is a good summary of how well our model does on this NER task, as it not only counts what entities the model correctly identified, but also considers if their labels are correct as well.\n", "\n", "F1 score is the harmonic mean of *precision*, the ratio of correctly identified entities (true positives, TP) and all identified entities (the sum of true positives and false positives, FP), and *recall*, the ratio of correctly identified entities (TP) and all true entities (true positives and false negatives, FN).\n", "\n", "$Precision = \\frac{TP}{TP+FP}$\n", "\n", "$Recall = \\frac{TP}{TP+FN}$\n", "\n", "$F1 = \\frac{2*Precision*Recall}{Precision+Recall} = \\frac{2*TP}{2*TP+FP+FN}$" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "len(valid_post_training)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "valid_post_training[0]" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "import spacy\n", "\n", "trained_nlp = spacy.load(\"models/model-best\")" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "doc = trained_nlp(valid_post_training[0][0])\n", "predicted_ents = [(d.text, d.label_) for d in doc.ents]\n", "predicted_ents" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "true_ents_raw = valid_post_training[0][1][\"entities\"]\n", "true_ents = []\n", "for t_e in true_ents_raw:\n", " text = valid_post_training[0][0][t_e[0] : t_e[1]]\n", " true_ents.append((text, t_e[-1]))\n", "\n", "true_ents" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "predicted_set = set(predicted_ents)\n", "true_set = set(true_ents)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "true_positives = len(predicted_set.intersection(true_set))\n", "false_positives = len(predicted_set - true_set)\n", "false_negatives = len(true_set - predicted_set)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "precision = true_positives / (true_positives + false_positives)\n", "recall = true_positives / (true_positives + false_negatives)\n", "f1_score = 2 * (precision * recall) / (precision + recall)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "precision, recall, f1_score" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "def eval_one(i):\n", " doc = trained_nlp(valid_post_training[i][0])\n", " predicted_ents = [(d.text, d.label_) for d in doc.ents]\n", "\n", " true_ents_raw = valid_post_training[i][1][\"entities\"]\n", " true_ents = []\n", " for t_e in true_ents_raw:\n", " text = valid_post_training[i][0][t_e[0] : t_e[1]]\n", " true_ents.append((text, t_e[-1]))\n", "\n", " predicted_set = set(predicted_ents)\n", " true_set = set(true_ents)\n", "\n", " true_positives = predicted_set.intersection(true_set)\n", " false_positives = predicted_set - true_set\n", " false_negatives = true_set - predicted_set\n", "\n", " return len(true_positives), len(false_positives), len(false_negatives)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "from tqdm import tqdm # for the progress bar\n", "\n", "all_true_positives = 0\n", "all_false_positives = 0\n", "all_false_negatives = 0\n", "\n", "for i in tqdm(range(len(valid_post_training))):\n", " if \"entities\" in valid_post_training[i][1]:\n", " tp, fp, fn = eval_one(i)\n", " all_true_positives += tp\n", " all_false_positives += fp\n", " all_false_negatives += fn\n", "\n", "precision = all_true_positives / (all_true_positives + all_false_positives)\n", "recall = all_true_positives / (all_true_positives + all_false_negatives)\n", "f1_score = 2 * (precision * recall) / (precision + recall)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "round(f1_score, 3) # not too shabby!" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Yay! We did it!" ] } ], "metadata": { "colab": { "collapsed_sections": [ "EX_0ickOW0CF" ], "provenance": [] }, "kernelspec": { "display_name": "Python 3", "name": "python3" }, "language_info": { "name": "python" }, "mystnb": { "execution_mode": "off" } }, "nbformat": 4, "nbformat_minor": 0 }