Project: s41467-024-55291-x | Function: Ner.train (python)

system: You are an expert Python programmer implementing algorithms from scientific papers.
Complete the masked function based on the algorithm description.
Follow Python best practices and maintain consistent code style.
Return **only** the complete Python function code.

user: Complete this Python function:

**DESCRIPTION:** Named Entity Recognition model training for identifying radiotracers and target proteins in nuclear medicine literature. This function implements the second CNN described in the paper for extracting radiotracer-protein associations from PubMed abstracts.

**KEY TERMS:** {
  "RADIOTRACER_S": "Short radiotracer entities",
  "RADIOTRACER_L": "Long radiotracer entities",
  "ner": "Named Entity Recognition pipeline",
  "optimizer": "Custom ADAM optimizer",
  "conv_window": "Convolutional window size"
}

**CONTEXT CODE:**     def __init__(self):
        """
        Constructor method.
        """

        #Load nlp model and initialise attributes.
        self.model = en_ner_jnlpba_md.load()
        self.output_path = None
        self.ner = self.model.get_pipe("ner")
        self.train_data = None
        self.test_data = None
        self.losses = {}
        self.scores = {}
        self.metrics = {}

    @timer('Loading NER training data... ')
    def load_train_data(self, input_path):
        """
        Loads training data from .json file

        :param input_path: path to .json file containing training data
        :type input_path: str
        """

        json_to_train_ents = []
        with open(input_path, "r") as f:
            json_list = list(f)
            for json_str in json_list:
                result = json.loads(json_str)
                json_to_train_ents.append(result)
        self.train_data = json_to_train_ents[0]

    @timer('Loading NER test data... ')
    def load_test_data(self, input_path):
        """
        Loads test data from .json file

        :param input_path: path to .json file containing test data
        :type input_path: str
        """

        json_to_test_ents = []
        with open(input_path, "r") as f:
            json_list = list(f)
            for json_str in json_list:
                result = json.loads(json_str)
                json_to_test_ents.append(result)
        self.test_data = json_to_test_ents[0]

    @timer('Training NER model...\n')
    def train(self, n_iter=20):
        """
        Trains spaCy's NER model

        :param n_iter: number of iterations for the model training, defaults to 20
        :type n_iter: int, optional
        """
        raise NotImplementedError("Function implementation has been masked for Ner.train")

    def evaluate(self):
        """
        Computes and stores the training results of the model
        """

        labels = ['RADIOTRACER_S', 'RADIOTRACER_L']
        for label in labels:
            self.scores[label] = {}

        tp = 0.0
        fp = 1e-8
        fn = 1e-8

        scorer = Scorer()
        for input_, annot in self.test_data:
            doc_gold_text = self.model.make_doc(input_)
            gold = GoldParse(doc_gold_text, entities=annot["entities"])
            pred_value = self.model(input_)
            scorer.score(pred_value, gold)

        # Update RADIOTRACER entity scores (only)
        for label in labels:
            if label in scorer.ner_per_ents:
                self.scores[label]['precision'] = scorer.ner_per_ents[label].precision
                self.scores[label]['recall'] = scorer.ner_per_ents[label].recall
                self.scores[label]['fscore'] = scorer.ner_per_ents[label].fscore

                tp += scorer.ner_per_ents[label].tp
                fp += scorer.ner_per_ents[label].fp
                fn += scorer.ner_per_ents[label].fn

        precision = tp / (tp + fp)
        recall = tp / (tp + fn)
        if (precision + recall) == 0:
            fscore = 0.0
        else:
            fscore = 2 * (precision * recall) / (precision + recall)

        self.scores['RADIOTRACER'] = {'precision': precision,
                                      'recall': recall,
                                      'fscore': fscore}

    @timer('Saving NER model to disk... ')
    def savemodel(self, output_path='ner_model'):
        """
        Saves the model to an output directory

        :param output_path: path to the directory that will contain the NER model files
        :type output_path: str
        """

        self.output_path = os.path.abspath(output_path)
        self.model.to_disk(self.output_path)

Complete the function implementation replacing "raise NotImplementedError".