docstr: ```
  Finetune a QA model.
  
  Args:
    data(list): list of dictionaries of the form:
                [{'question': 'What is ktrain?'
                 'context': 'ktrain is a low-code library for augmented machine learning.'
                 'answer': 'ktrain'}]
    epochs(int): number of epochs.  Default:3
    learning_rate(float): learning rate.  Default: 2e-5
  Returns:
    fine-tuned model (i.e., QAFineTuner.model)
  ```
docstr_tok: ["``","`","Finetune","a","QA","model",".","Args",":","data","(","list",")",":","list","of","dictionaries","of","the","form",":","[","{","'question","'",":","'What","is","ktrain","?","'","'context","'",":","'ktrain","is","a","low-code","library","for","augmented","machine","learning",".","'","'answer","'",":","'ktrain","'","}","]","epochs","(","int",")",":","number","of","epochs",".","Default:3","learning_rate","(","float",")",":","learning","rate",".","Default",":","2e-5","Returns",":","fine-tuned","model","(","i.e.",",","QAFineTuner.model",")","``","`"]
code: def finetune(
      self,
      data,
      epochs=3,
      learning_rate=2e-5,
      batch_size=8,
      max_seq_length=512,
  ):
      """
      ```
      Finetune a QA model.
  
      Args:
        data(list): list of dictionaries of the form:
                    [{'question': 'What is ktrain?'
                     'context': 'ktrain is a low-code library for augmented machine learning.'
                     'answer': 'ktrain'}]
        epochs(int): number of epochs.  Default:3
        learning_rate(float): learning rate.  Default: 2e-5
      Returns:
        fine-tuned model (i.e., QAFineTuner.model)
      ```
      """
      if self.model.name_or_path.startswith("bert-large"):
          warnings.warn(
              "You are fine-tuning a bert-large-* model, which requires lots of memory.  "
              + "If you get a memory error, switch to distilbert-base-cased-distilled-squad."
          )
      if batch_size > len(data):
          batch_size = len(data)
  
      model = self.model
      tokenizer = self.tokenizer
      question_column_name = "question"
      context_column_name = "context"
      answer_column_name = "answers"
  
      # Padding side determines if we do (question|context) or (context|question).
      pad_on_right = tokenizer.padding_side == "right"
  
      # Training preprocessing
      def prepare_train_features(examples):
          # Some of the questions have lots of whitespace on the left, which is not useful and will make the
          # truncation of the context fail (the tokenized question will take a lots of space). So we remove that
          # left whitespace
          examples[question_column_name] = [
              q.lstrip() for q in examples[question_column_name]
          ]
  
          # Tokenize our examples with truncation and maybe padding, but keep the overflows using a stride. This results
          # in one example possible giving several features when a context is long, each of those features having a
          # context that overlaps a bit the context of the previous feature.
          tokenized_examples = tokenizer(
              examples[
                  question_column_name
                  if pad_on_right
                  else context_column_name
              ],
              examples[
                  context_column_name
                  if pad_on_right
                  else question_column_name
              ],
              truncation="only_second"
              if pad_on_right
              else "only_first",
              max_length=max_seq_length,
              stride=128,
              return_overflowing_tokens=True,
              return_offsets_mapping=True,
              # padding="max_length" if data_args.pad_to_max_length else False,
              padding="max_length",
          )
  
          # Since one example might give us several features if it has a long context, we need a map from a feature to
          # its corresponding example. This key gives us just that.
          sample_mapping = tokenized_examples.pop(
              "overflow_to_sample_mapping"
          )
          # The offset mappings will give us a map from token to character position in the original context. This will
          # help us compute the start_positions and end_positions.
          offset_mapping = tokenized_examples.pop("offset_mapping")
  
          # Let's label those examples!
          tokenized_examples["start_positions"] = []
          tokenized_examples["end_positions"] = []
  
          for i, offsets in enumerate(offset_mapping):
              # We will label impossible answers with the index of the CLS token.
              input_ids = tokenized_examples["input_ids"][i]
              cls_index = input_ids.index(tokenizer.cls_token_id)
  
              # Grab the sequence corresponding to that example (to know what is the context and what is the question).
              sequence_ids = tokenized_examples.sequence_ids(i)
  
              # One example can give several spans, this is the index of the example containing this span of text.
              sample_index = sample_mapping[i]
              answers = examples[answer_column_name][sample_index]
              # If no answers are given, set the cls_index as answer.
              if len(answers["answer_start"]) == 0:
                  tokenized_examples["start_positions"].append(
                      cls_index
                  )
                  tokenized_examples["end_positions"].append(cls_index)
              else:
                  # Start/end character index of the answer in the text.
                  start_char = answers["answer_start"][0]
                  end_char = start_char + len(answers["text"][0])
  
                  # Start token index of the current span in the text.
                  token_start_index = 0
                  while sequence_ids[token_start_index] != (
                      1 if pad_on_right else 0
                  ):
                      token_start_index += 1
  
                  # End token index of the current span in the text.
                  token_end_index = len(input_ids) - 1
                  while sequence_ids[token_end_index] != (
                      1 if pad_on_right else 0
                  ):
                      token_end_index -= 1
  
                  # Detect if the answer is out of the span (in which case this feature is labeled with the CLS index).
                  if not (
                      offsets[token_start_index][0] <= start_char
                      and offsets[token_end_index][1] >= end_char
                  ):
                      tokenized_examples["start_positions"].append(
                          cls_index
                      )
                      tokenized_examples["end_positions"].append(
                          cls_index
                      )
                  else:
                      # Otherwise move the token_start_index and token_end_index to the two ends of the answer.
                      # Note: we could go after the last offset if the answer is the last word (edge case).
                      while (
                          token_start_index < len(offsets)
                          and offsets[token_start_index][0]
                          <= start_char
                      ):
                          token_start_index += 1
                      tokenized_examples["start_positions"].append(
                          token_start_index - 1
                      )
                      while offsets[token_end_index][1] >= end_char:
                          token_end_index -= 1
                      tokenized_examples["end_positions"].append(
                          token_end_index + 1
                      )
  
          return tokenized_examples
  
      dataset = convert_to_dataset(data)
      print(dataset)
      print(dataset[0])
  
      train_dataset = dataset.map(
          prepare_train_features,
          batched=True,
          num_proc=1,
      )
      optimizer = tf.keras.optimizers.Adam(learning_rate=learning_rate)
  
      def dummy_loss(y_true, y_pred):
          return tf.reduce_mean(y_pred)
  
      losses = {"loss": dummy_loss}
      model.compile(optimizer=optimizer, loss=losses)
  
      training_dataset = convert_dataset_for_tensorflow(
          train_dataset,
          batch_size=batch_size,
          dataset_mode="variable_batch",
          drop_remainder=True,
          shuffle=True,
      )
      model.fit(training_dataset, epochs=int(epochs))
  
file_path: ktrain/ktrain/text/qa/qa_finetuner.py
imports: import warnings
  import pandas
  import tensorflow
label_desc: Use image node_id 2 for calling the QAFineTuner obj's underlying member method code with example usage: obj.finetune(data, epochs, learning_rate, batch_size, max_seq_length) and returns: tokenized_examples, tf