mirror of
https://github.com/explosion/spaCy.git
synced 2025-01-12 10:16:27 +03:00
Improved training and evaluation (#3538)
* Add early stopping * Add return_score option to evaluate * Fix missing str to path conversion * Fix import + old python compatibility * Fix bad beam_width setting during cpu evaluation in spacy train with gpu option turned on
This commit is contained in:
parent
bbf6f9f764
commit
cc1516ec26
|
@ -17,6 +17,7 @@ from .. import displacy
|
||||||
gpu_id=("Use GPU", "option", "g", int),
|
gpu_id=("Use GPU", "option", "g", int),
|
||||||
displacy_path=("Directory to output rendered parses as HTML", "option", "dp", str),
|
displacy_path=("Directory to output rendered parses as HTML", "option", "dp", str),
|
||||||
displacy_limit=("Limit of parses to render as HTML", "option", "dl", int),
|
displacy_limit=("Limit of parses to render as HTML", "option", "dl", int),
|
||||||
|
return_scores=("Return dict containing model scores", "flag", "r", bool),
|
||||||
)
|
)
|
||||||
def evaluate(
|
def evaluate(
|
||||||
model,
|
model,
|
||||||
|
@ -25,6 +26,7 @@ def evaluate(
|
||||||
gold_preproc=False,
|
gold_preproc=False,
|
||||||
displacy_path=None,
|
displacy_path=None,
|
||||||
displacy_limit=25,
|
displacy_limit=25,
|
||||||
|
return_scores=False,
|
||||||
):
|
):
|
||||||
"""
|
"""
|
||||||
Evaluate a model. To render a sample of parses in a HTML file, set an
|
Evaluate a model. To render a sample of parses in a HTML file, set an
|
||||||
|
@ -75,6 +77,8 @@ def evaluate(
|
||||||
ents=render_ents,
|
ents=render_ents,
|
||||||
)
|
)
|
||||||
msg.good("Generated {} parses as HTML".format(displacy_limit), displacy_path)
|
msg.good("Generated {} parses as HTML".format(displacy_limit), displacy_path)
|
||||||
|
if return_scores:
|
||||||
|
return scorer.scores
|
||||||
|
|
||||||
|
|
||||||
def render_parses(docs, output_path, model_name="", limit=250, deps=True, ents=True):
|
def render_parses(docs, output_path, model_name="", limit=250, deps=True, ents=True):
|
||||||
|
|
|
@ -35,6 +35,7 @@ from .. import about
|
||||||
pipeline=("Comma-separated names of pipeline components", "option", "p", str),
|
pipeline=("Comma-separated names of pipeline components", "option", "p", str),
|
||||||
vectors=("Model to load vectors from", "option", "v", str),
|
vectors=("Model to load vectors from", "option", "v", str),
|
||||||
n_iter=("Number of iterations", "option", "n", int),
|
n_iter=("Number of iterations", "option", "n", int),
|
||||||
|
early_stopping_iter=("Maximum number of training epochs without dev accuracy improvement", "option", "e", int),
|
||||||
n_examples=("Number of examples", "option", "ns", int),
|
n_examples=("Number of examples", "option", "ns", int),
|
||||||
use_gpu=("Use GPU", "option", "g", int),
|
use_gpu=("Use GPU", "option", "g", int),
|
||||||
version=("Model version", "option", "V", str),
|
version=("Model version", "option", "V", str),
|
||||||
|
@ -74,6 +75,7 @@ def train(
|
||||||
pipeline="tagger,parser,ner",
|
pipeline="tagger,parser,ner",
|
||||||
vectors=None,
|
vectors=None,
|
||||||
n_iter=30,
|
n_iter=30,
|
||||||
|
early_stopping_iter=None,
|
||||||
n_examples=0,
|
n_examples=0,
|
||||||
use_gpu=-1,
|
use_gpu=-1,
|
||||||
version="0.0.0",
|
version="0.0.0",
|
||||||
|
@ -101,6 +103,7 @@ def train(
|
||||||
train_path = util.ensure_path(train_path)
|
train_path = util.ensure_path(train_path)
|
||||||
dev_path = util.ensure_path(dev_path)
|
dev_path = util.ensure_path(dev_path)
|
||||||
meta_path = util.ensure_path(meta_path)
|
meta_path = util.ensure_path(meta_path)
|
||||||
|
output_path = util.ensure_path(output_path)
|
||||||
if raw_text is not None:
|
if raw_text is not None:
|
||||||
raw_text = list(srsly.read_jsonl(raw_text))
|
raw_text = list(srsly.read_jsonl(raw_text))
|
||||||
if not train_path or not train_path.exists():
|
if not train_path or not train_path.exists():
|
||||||
|
@ -222,6 +225,8 @@ def train(
|
||||||
msg.row(row_head, **row_settings)
|
msg.row(row_head, **row_settings)
|
||||||
msg.row(["-" * width for width in row_settings["widths"]], **row_settings)
|
msg.row(["-" * width for width in row_settings["widths"]], **row_settings)
|
||||||
try:
|
try:
|
||||||
|
iter_since_best = 0
|
||||||
|
best_score = 0.
|
||||||
for i in range(n_iter):
|
for i in range(n_iter):
|
||||||
train_docs = corpus.train_docs(
|
train_docs = corpus.train_docs(
|
||||||
nlp, noise_level=noise_level, gold_preproc=gold_preproc, max_length=0
|
nlp, noise_level=noise_level, gold_preproc=gold_preproc, max_length=0
|
||||||
|
@ -276,7 +281,9 @@ def train(
|
||||||
gpu_wps = nwords / (end_time - start_time)
|
gpu_wps = nwords / (end_time - start_time)
|
||||||
with Model.use_device("cpu"):
|
with Model.use_device("cpu"):
|
||||||
nlp_loaded = util.load_model_from_path(epoch_model_path)
|
nlp_loaded = util.load_model_from_path(epoch_model_path)
|
||||||
nlp_loaded.parser.cfg["beam_width"]
|
for name, component in nlp_loaded.pipeline:
|
||||||
|
if hasattr(component, "cfg"):
|
||||||
|
component.cfg["beam_width"] = beam_width
|
||||||
dev_docs = list(
|
dev_docs = list(
|
||||||
corpus.dev_docs(nlp_loaded, gold_preproc=gold_preproc)
|
corpus.dev_docs(nlp_loaded, gold_preproc=gold_preproc)
|
||||||
)
|
)
|
||||||
|
@ -328,6 +335,18 @@ def train(
|
||||||
gpu_wps=gpu_wps,
|
gpu_wps=gpu_wps,
|
||||||
)
|
)
|
||||||
msg.row(progress, **row_settings)
|
msg.row(progress, **row_settings)
|
||||||
|
# early stopping
|
||||||
|
if early_stopping_iter is not None:
|
||||||
|
current_score = _score_for_model(meta)
|
||||||
|
if current_score < best_score:
|
||||||
|
iter_since_best += 1
|
||||||
|
else:
|
||||||
|
iter_since_best = 0
|
||||||
|
best_score = current_score
|
||||||
|
if iter_since_best >= early_stopping_iter:
|
||||||
|
msg.text("Early stopping, best iteration is: {}".format(i-iter_since_best))
|
||||||
|
msg.text("Best score = {}; Final iteration score = {}".format(best_score, current_score))
|
||||||
|
break
|
||||||
finally:
|
finally:
|
||||||
with nlp.use_params(optimizer.averages):
|
with nlp.use_params(optimizer.averages):
|
||||||
final_model_path = output_path / "model-final"
|
final_model_path = output_path / "model-final"
|
||||||
|
@ -337,6 +356,18 @@ def train(
|
||||||
best_model_path = _collate_best_model(meta, output_path, nlp.pipe_names)
|
best_model_path = _collate_best_model(meta, output_path, nlp.pipe_names)
|
||||||
msg.good("Created best model", best_model_path)
|
msg.good("Created best model", best_model_path)
|
||||||
|
|
||||||
|
def _score_for_model(meta):
|
||||||
|
""" Returns mean score between tasks in pipeline that can be used for early stopping. """
|
||||||
|
mean_acc = list()
|
||||||
|
pipes = meta['pipeline']
|
||||||
|
acc = meta['accuracy']
|
||||||
|
if 'tagger' in pipes:
|
||||||
|
mean_acc.append(acc['tags_acc'])
|
||||||
|
if 'parser' in pipes:
|
||||||
|
mean_acc.append((acc['uas']+acc['las']) / 2)
|
||||||
|
if 'ner' in pipes:
|
||||||
|
mean_acc.append((acc['ents_p']+acc['ents_r']+acc['ents_f']) / 3)
|
||||||
|
return sum(mean_acc) / len(mean_acc)
|
||||||
|
|
||||||
@contextlib.contextmanager
|
@contextlib.contextmanager
|
||||||
def _create_progress_bar(total):
|
def _create_progress_bar(total):
|
||||||
|
|
Loading…
Reference in New Issue
Block a user