mirror of
				https://github.com/explosion/spaCy.git
				synced 2025-11-04 09:57:26 +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