text: """BERT classifier. Example taken and modified from Huggingface's BERT Pytorch repository."""
  
  import csv
  import os
  import logging
  import argparse
  import random
  from tqdm import tqdm, trange
  
  import numpy as np
  import torch
  from torch.utils.data import TensorDataset, DataLoader, RandomSampler, SequentialSampler
  from torch.utils.data.distributed import DistributedSampler
  
  from pytorch_pretrained_bert.tokenization import BertTokenizer
  from modeling import BertForSequenceClassification
  from pytorch_pretrained_bert.optimization import BertAdam
  from pytorch_pretrained_bert.file_utils import PYTORCH_PRETRAINED_BERT_CACHE
  
  import pandas as pd
  from sklearn.model_selection import train_test_split
  from sklearn.metrics import accuracy_score, confusion_matrix, classification_report
  import nltk
  from nltk.tokenize import TweetTokenizer
  from nltk.corpus import stopwords
  
  import matplotlib.pyplot as plt
  
  
  logging.basicConfig(format = '%(asctime)s - %(levelname)s - %(name)s -   %(message)s',
                      datefmt = '%m/%d/%Y %H:%M:%S',
                      level = logging.INFO)
  logger = logging.getLogger(__name__)
  
  
  class InputExample(object):
      """A single training/test example for simple sequence classification."""
  
      def __init__(self, guid, text_a, text_b=None, label=None):
          """Constructs a InputExample.
  
          Args:
              guid: Unique id for the example.
              text_a: string. The untokenized text of the first sequence. For single
              sequence tasks, only this sequence must be specified.
              text_b: (Optional) string. The untokenized text of the second sequence.
              Only must be specified for sequence pair tasks.
              label: (Optional) string. The label of the example. This should be
              specified for train and dev examples, but not for test examples.
          """
          self.guid = guid
          self.text_a = text_a
          self.text_b = text_b
          self.label = label
  
  
  class InputFeatures(object):
      """A single set of features of data."""
  
      def __init__(self, input_ids, input_mask, segment_ids, label_id):
          self.input_ids = input_ids
          self.input_mask = input_mask
          self.segment_ids = segment_ids
          self.label_id = label_id
  
  
  class DataProcessor(object):
      """Base class for data converters for sequence classification data sets."""
  
      def get_train_examples(self):
          """Gets a collection of `InputExample`s for the train set."""
          raise NotImplementedError()
  
      def get_dev_examples(self):
          """Gets a collection of `InputExample`s for the dev set."""
          raise NotImplementedError()
  
      def get_labels(self):
          """Gets the list of labels for this data set."""
          raise NotImplementedError()
  
  class HateSpeechProcessor(DataProcessor):
      """Class for data converters for hate speech data sets."""
  
      def __init__(self, train_dataset, test_dataset):
          self.train_split = None
          self.train_labels = None
          self.test_split = None
          self.test_labels = None
          self._split_train_test("data/{}.csv".format(train_dataset), "data/{}.csv".format(test_dataset))
  
      def get_train_examples(self):
          return self._create_examples(self.train_split, self.train_labels)
  
      def get_dev_examples(self):
          return self._create_examples(self.test_split, self.test_labels)
  
      def get_labels(self):
          return ["0", "1", "2"]
  
      def _split_train_test(self, train_dir, test_dir):
          if train_dir == test_dir:
              data = pd.read_csv(train_dir)
              train_split, dev_split, train_labels, dev_labels = train_test_split(
                  data.tweet,
                  data.label,
                  train_size=0.8,
                  test_size=0.2,
                  stratify=data.label,
                  shuffle=True
              )
          else:
              train_data = pd.read_csv(train_dir)
              test_data = pd.read_csv(test_dir)
              train_split = train_data.tweet
              train_labels = train_data.label
              dev_split = test_data.tweet
              dev_labels = test_data.label
          self.train_split = dict(train_split)
          self.train_labels = dict(train_labels)
          self.test_split = dict(dev_split)
          self.test_labels = dict(dev_labels)
  
      def _create_examples(self, tweets, labels):
          examples = []
          for guid, tweet in tweets.items():
              tknz = TweetTokenizer(strip_handles=True)
              tknz_tweet = tknz.tokenize(tweet)
              tknz_tweet = [word for word in tknz_tweet
                  if
                  word.isalpha() and
                  word != "RT"]
              examples.append(
                  InputExample(guid=guid, text_a=' '.join(tknz_tweet), label=str(labels[guid]))
              )
          return examples
  
  def convert_examples_to_features(examples, label_list, max_seq_length, tokenizer):
      """Loads a data file into a list of `InputBatch`s."""
  
      label_map = {label : i for i, label in enumerate(label_list)}
  
      features = []
      for (ex_index, example) in enumerate(examples):
          tokens_a = tokenizer.tokenize(example.text_a)
  
          if len(tokens_a) > max_seq_length - 2:
              tokens_a = tokens_a[:(max_seq_length - 2)]
  
          tokens = ["[CLS]"] + tokens_a + ["[SEP]"]
          segment_ids = [0] * len(tokens)
  
          input_ids = tokenizer.convert_tokens_to_ids(tokens)
  
          input_mask = [1] * len(input_ids)
  
          padding = [0] * (max_seq_length - len(input_ids))
          input_ids += padding
          input_mask += padding
          segment_ids += padding
  
          assert len(input_ids) == max_seq_length
          assert len(input_mask) == max_seq_length
          assert len(segment_ids) == max_seq_length
  
          label_id = label_map[example.label]
          if ex_index < 5:
              logger.info("*** Example ***")
              logger.info("guid: %s" % (example.guid))
              logger.info("tokens: %s" % " ".join(
                      [str(x) for x in tokens]))
              logger.info("input_ids: %s" % " ".join([str(x) for x in input_ids]))
              logger.info("input_mask: %s" % " ".join([str(x) for x in input_mask]))
              logger.info(
                      "segment_ids: %s" % " ".join([str(x) for x in segment_ids]))
              logger.info("label: %s (id = %d)" % (example.label, label_id))
  
          features.append(
                  InputFeatures(input_ids=input_ids,
                                input_mask=input_mask,
                                segment_ids=segment_ids,
                                label_id=label_id))
      return features
  
  
  def accuracy(out, labels):
      outputs = np.argmax(out, axis=1)
      return np.sum(outputs == labels)
  
  def warmup_linear(x, warmup=0.002):
      if x < warmup:
          return x/warmup
      return 1.0 - x
  
  def main():
      parser = argparse.ArgumentParser()
  
      parser.add_argument("--bert_model", default=None, type=str, required=True,
                          help="Bert pre-trained model selected in the list: bert-base-uncased, "
                          "bert-large-uncased, bert-base-cased, bert-large-cased, bert-base-multilingual-uncased, "
                          "bert-base-multilingual-cased, bert-base-chinese.")
      parser.add_argument("--task_name",
                          default=None,
                          type=str,
                          required=True,
                          help="The name of the task to train.")
      parser.add_argument("--output_dir",
                          default=None,
                          type=str,
                          required=True,
                          help="The output directory where the model predictions and checkpoints will be written.")
      parser.add_argument("--train_dataset",
                          default=None,
                          type=str,
                          required=True,
                          help="The train dataset to use.")
      parser.add_argument("--test_dataset",
                          default=None,
                          type=str,
                          required=True,
                          help="The test dataset to use.")
  
      parser.add_argument("--max_seq_length",
                          default=128,
                          type=int,
                          help="The maximum total input sequence length after WordPiece tokenization. \n"
                               "Sequences longer than this will be truncated, and sequences shorter \n"
                               "than this will be padded.")
      parser.add_argument("--do_train",
                          action='store_true',
                          help="Whether to run training.")
      parser.add_argument("--do_eval",
                          action='store_true',
                          help="Whether to run eval on the dev set.")
      parser.add_argument("--do_lower_case",
                          action='store_true',
                          help="Set this flag if you are using an uncased model.")
      parser.add_argument("--train_batch_size",
                          default=32,
                          type=int,
                          help="Total batch size for training.")
      parser.add_argument("--eval_batch_size",
                          default=8,
                          type=int,
                          help="Total batch size for eval.")
      parser.add_argument("--learning_rate",
                          default=5e-5,
                          type=float,
                          help="The initial learning rate for Adam.")
      parser.add_argument("--num_train_epochs",
                          default=3.0,
                          type=float,
                          help="Total number of training epochs to perform.")
      parser.add_argument("--warmup_proportion",
                          default=0.1,
                          type=float,
                          help="Proportion of training to perform linear learning rate warmup for. "
                               "E.g., 0.1 = 10%% of training.")
      parser.add_argument('--seed',
                          type=int,
                          default=42,
                          help="random seed for initialization")
      parser.add_argument('--gradient_accumulation_steps',
                          type=int,
                          default=1,
                          help="Number of updates steps to accumulate before performing a backward/update pass.")
  
  
      args = parser.parse_args()
  
      processors = {
          "hsp": HateSpeechProcessor,
      }
  
      num_labels_task = {
          "hsp": 3,
      }
  
  
      device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
      n_gpu = torch.cuda.device_count()
  
      logger.info("device: {} n_gpu: {}".format(device, n_gpu))
  
      if args.gradient_accumulation_steps < 1:
          raise ValueError("Invalid gradient_accumulation_steps parameter: {}, should be >= 1".format(
                              args.gradient_accumulation_steps))
  
      args.train_batch_size = int(args.train_batch_size / args.gradient_accumulation_steps)
  
      random.seed(args.seed)
      np.random.seed(args.seed)
      torch.manual_seed(args.seed)
      if n_gpu > 0:
          torch.cuda.manual_seed_all(args.seed)
  
      if not args.do_train and not args.do_eval:
          raise ValueError("At least one of `do_train` or `do_eval` must be True.")
  
      #if os.path.exists(args.output_dir) and os.listdir(args.output_dir) and args.do_train:
      #    raise ValueError("Output directory ({}) already exists and is not empty.".format(args.output_dir))
      os.makedirs(args.output_dir, exist_ok=True)
  
      task_name = args.task_name.lower()
  
      if task_name not in processors:
          raise ValueError("Task not found: %s" % (task_name))
  
      processor = processors[task_name](args.train_dataset, args.test_dataset)
      num_labels = num_labels_task[task_name]
      label_list = processor.get_labels()
  
      tokenizer = BertTokenizer.from_pretrained(args.bert_model, do_lower_case=args.do_lower_case)
  
      train_examples = None
      num_train_steps = None
      if args.do_train:
          train_examples = processor.get_train_examples()
          num_train_steps = int(
              len(train_examples) / args.train_batch_size / args.gradient_accumulation_steps * args.num_train_epochs)
  
      model = BertForSequenceClassification.from_pretrained(args.bert_model,
                cache_dir=PYTORCH_PRETRAINED_BERT_CACHE / 'distributed_-1',
                num_labels = num_labels)
  
      model.to(device)
      if n_gpu > 1:
          model = torch.nn.DataParallel(model)
  
      param_optimizer = list(model.named_parameters())
      no_decay = ['bias', 'LayerNorm.bias', 'LayerNorm.weight']
      optimizer_grouped_parameters = [
          {'params': [p for n, p in param_optimizer if not any(nd in n for nd in no_decay)], 'weight_decay': 0.01},
          {'params': [p for n, p in param_optimizer if any(nd in n for nd in no_decay)], 'weight_decay': 0.0}
          ]
      t_total = num_train_steps
  
      optimizer = BertAdam(optimizer_grouped_parameters,
                           lr=args.learning_rate,
                           warmup=args.warmup_proportion,
                           t_total=t_total)
  
  
      global_step = 0
      nb_tr_steps = 0
      tr_loss = 0
      tr_losses = []
      if args.do_train:
          train_features = convert_examples_to_features(
              train_examples, label_list, args.max_seq_length, tokenizer)
          logger.info("***** Running training *****")
          logger.info("  Num examples = %d", len(train_examples))
          logger.info("  Batch size = %d", args.train_batch_size)
          logger.info("  Num steps = %d", num_train_steps)
          all_input_ids = torch.tensor([f.input_ids for f in train_features], dtype=torch.long)
          all_input_mask = torch.tensor([f.input_mask for f in train_features], dtype=torch.long)
          all_segment_ids = torch.tensor([f.segment_ids for f in train_features], dtype=torch.long)
          all_label_ids = torch.tensor([f.label_id for f in train_features], dtype=torch.long)
          train_data = TensorDataset(all_input_ids, all_input_mask, all_segment_ids, all_label_ids)
          train_sampler = RandomSampler(train_data)
          train_dataloader = DataLoader(train_data, sampler=train_sampler, batch_size=args.train_batch_size)
  
          model.train()
          for i in trange(int(args.num_train_epochs), desc="Epoch"):
              tr_loss = 0
              nb_tr_examples, nb_tr_steps = 0, 0
              for step, batch in enumerate(tqdm(train_dataloader, desc="Iteration")):
                  batch = tuple(t.to(device) for t in batch)
                  input_ids, input_mask, segment_ids, label_ids = batch
                  loss = model(input_ids, segment_ids, input_mask, label_ids)
                  if n_gpu > 1:
                      loss = loss.mean()
                  if args.gradient_accumulation_steps > 1:
                      loss = loss / args.gradient_accumulation_steps
  
                  loss.backward()
  
                  tr_loss += loss.item()
                  tr_losses.append(loss.item())
                  nb_tr_examples += input_ids.size(0)
                  nb_tr_steps += 1
                  if (step + 1) % args.gradient_accumulation_steps == 0:
                      lr_this_step = args.learning_rate * warmup_linear(global_step/t_total, args.warmup_proportion)
                      for param_group in optimizer.param_groups:
                          param_group['lr'] = lr_this_step
                      optimizer.step()
                      optimizer.zero_grad()
                      global_step += 1
  
      model_to_save = model.module if hasattr(model, 'module') else model
      output_model_file = os.path.join(args.output_dir, "pytorch_model.bin")
      if args.do_train:
          torch.save(model_to_save.state_dict(), output_model_file)
  
      model_state_dict = torch.load(output_model_file)
      model = BertForSequenceClassification.from_pretrained(args.bert_model, state_dict=model_state_dict, num_labels=num_labels)
      model.to(device)
  
      if args.do_eval:
          eval_examples = processor.get_dev_examples()
          eval_features = convert_examples_to_features(
              eval_examples, label_list, args.max_seq_length, tokenizer)
          logger.info("***** Running evaluation *****")
          logger.info("  Num examples = %d", len(eval_examples))
          logger.info("  Batch size = %d", args.eval_batch_size)
          all_input_ids = torch.tensor([f.input_ids for f in eval_features], dtype=torch.long)
          all_input_mask = torch.tensor([f.input_mask for f in eval_features], dtype=torch.long)
          all_segment_ids = torch.tensor([f.segment_ids for f in eval_features], dtype=torch.long)
          all_label_ids = torch.tensor([f.label_id for f in eval_features], dtype=torch.long)
          eval_data = TensorDataset(all_input_ids, all_input_mask, all_segment_ids, all_label_ids)
          eval_sampler = SequentialSampler(eval_data)
          eval_dataloader = DataLoader(eval_data, sampler=eval_sampler, batch_size=args.eval_batch_size)
  
          model.eval()
          eval_loss, eval_accuracy = 0, 0
          nb_eval_steps, nb_eval_examples = 0, 0
  
          pred = []
          labels = []
          eval_losses = []
  
          eval_dict = {}
          for eval_example in eval_examples:
              eval_dict[eval_example.guid] = eval_example.text_a
  
          print(eval_dict)
  
          for input_ids, input_mask, segment_ids, label_ids in tqdm(eval_dataloader, desc="Evaluating"):
              input_ids = input_ids.to(device)
              input_mask = input_mask.to(device)
              segment_ids = segment_ids.to(device)
              label_ids = label_ids.to(device)
  
              with torch.no_grad():
                  tmp_eval_loss = model(input_ids, segment_ids, input_mask, label_ids)
                  logits = model(input_ids, segment_ids, input_mask)
  
              logits = logits.detach().cpu().numpy()
              label_ids = label_ids.to('cpu').numpy()
              tmp_eval_accuracy = accuracy(logits, label_ids)
  
              # Hate vs offensive evaluation
              print("label ids:", label_ids.tolist())
              print("predicted:", np.argmax(logits, axis=1).tolist())
  
              pred += np.argmax(logits, axis=1).tolist()
              labels += label_ids.tolist()
              eval_losses.append(tmp_eval_loss.mean().item())
  
              eval_loss += tmp_eval_loss.mean().item()
              eval_accuracy += tmp_eval_accuracy
  
              nb_eval_examples += input_ids.size(0)
              nb_eval_steps += 1
  
          eval_loss = eval_loss / nb_eval_steps
          eval_accuracy = eval_accuracy / nb_eval_examples
          loss = tr_loss/nb_tr_steps if args.do_train else None
          result = {'eval_loss': eval_loss,
                    'eval_accuracy': eval_accuracy,
                    'global_step': global_step,
                    'loss': loss}
  
          target_names = ['hateful', 'offensive', 'normal']
          print("--- Test metrics ---")
          print("Accuarcy score:", accuracy_score(labels, pred))
          print("Confusion matrix:\n", confusion_matrix(labels, pred))
          print("Classification report:\n", classification_report(labels, pred, digits=3, target_names=target_names))
  
          output_eval_file = os.path.join(args.output_dir, "eval_results.txt")
          with open(output_eval_file, "w") as writer:
              logger.info("***** Eval results *****")
              for key in sorted(result.keys()):
                  logger.info("  %s = %s", key, str(result[key]))
                  writer.write("%s = %s\n" % (key, str(result[key])))
  
  if __name__ == "__main__":
      main()
  