code: from gensim.corpora.dictionary import Dictionary
  from gensim.models.coherencemodel import CoherenceModel
  from gensim.models import KeyedVectors
  import gensim.downloader as api
  from scipy.spatial.distance import cosine
  import abc
  
  from contextualized_topic_models.evaluation.rbo import rbo
  import numpy as np
  import itertools
  
  
  class Measure:
      def __init__(self):
          pass
  
      def score(self):
          pass
  
  
  class TopicDiversity(Measure):
      def __init__(self, topics):
          super().__init__()
          self.topics = topics
  
      def score(self, topk=25):
          """
          :param topk: topk words on which the topic diversity will be computed
          :return:
          """
          if topk > len(self.topics[0]):
              raise Exception('Words in topics are less than topk')
          else:
              unique_words = set()
              for t in self.topics:
                  unique_words = unique_words.union(set(t[:topk]))
              td = len(unique_words) / (topk * len(self.topics))
              return td
  
  
  class Coherence(abc.ABC):
      """
      :param topics: a list of lists of the top-k words
      :param texts: (list of lists of strings) represents the corpus on which the empirical frequencies of words are computed
      """
      def __init__(self, topics, texts):
          self.topics = topics
          self.texts = texts
          self.dictionary = Dictionary(self.texts)
  
      @abc.abstractmethod
      def score(self):
          pass
  
  
  class CoherenceNPMI(Coherence):
      def __init__(self, topics, texts):
          super().__init__(topics, texts)
  
      def score(self, topk=10, per_topic=False):
          """
          :param topk: how many most likely words to consider in the evaluation
          :param per_topic: if True, returns the coherence value for each topic (default: False)
          :return: NPMI coherence
          """
          if topk > len(self.topics[0]):
              raise Exception('Words in topics are less than topk')
          else:
              npmi = CoherenceModel(topics=self.topics, texts=self.texts, dictionary=self.dictionary,
                                    coherence='c_npmi', topn=topk)
              if per_topic:
                  return npmi.get_coherence_per_topic()
              else:
                  return npmi.get_coherence()
  
  class CoherenceUMASS(Coherence):
      def __init__(self, topics, texts):
          super().__init__(topics, texts)
  
      def score(self, topk=10, per_topic=False):
          """
          :param topk: how many most likely words to consider in the evaluation
          :param per_topic: if True, returns the coherence value for each topic (default: False)
          :return: UMass coherence
          """
          if topk > len(self.topics[0]):
              raise Exception('Words in topics are less than topk')
          else:
              umass = CoherenceModel(topics=self.topics, texts=self.texts, dictionary=self.dictionary,
                                     coherence='u_mass', topn=topk)
              if per_topic:
                  return umass.get_coherence_per_topic()
              else:
                  return umass.get_coherence()
  
  class CoherenceUCI(Coherence):
      def __init__(self, topics, texts):
          super().__init__(topics, texts)
  
      def score(self, topk=10, per_topic=False):
          """
          :param topk: how many most likely words to consider in the evaluation
          :param per_topic: if True, returns the coherence value for each topic (default: False)
          :return: UCI coherence
          """
          if topk > len(self.topics[0]):
              raise Exception('Words in topics are less than topk')
          else:
              uci = CoherenceModel(topics=self.topics, texts=self.texts, dictionary=self.dictionary,
                                   coherence='c_uci', topn=topk)
              if per_topic:
                  return uci.get_coherence_per_topic()
              else:
                  return uci.get_coherence()
  
  class CoherenceCV(Coherence):
      def __init__(self, topics, texts):
          super().__init__(topics, texts)
  
      def score(self, topk=10, per_topic=False):
          """
          :param topk: how many most likely words to consider in the evaluation
          :param per_topic: if True, returns the coherence value for each topic (default: False)
          :return: C_V coherence
          """
          if topk > len(self.topics[0]):
              raise Exception('Words in topics are less than topk')
          else:
              cv = CoherenceModel(topics=self.topics, texts=self.texts, dictionary=self.dictionary,
                                  coherence='c_v', topn=topk)
              if per_topic:
                  return cv.get_coherence_per_topic()
              else:
                  return cv.get_coherence()
  
  
  class CoherenceWordEmbeddings(Measure):
      def __init__(self, topics, word2vec_path=None, binary=False):
          """
          :param topics: a list of lists of the top-n most likely words
          :param word2vec_path: if word2vec_file is specified, it retrieves the word embeddings file (in word2vec format) to
           compute similarities between words, otherwise 'word2vec-google-news-300' is downloaded
          :param binary: if the word2vec file is binary
          """
          super().__init__()
          self.topics = topics
          self.binary = binary
          if word2vec_path is None:
              self.wv = api.load('word2vec-google-news-300')
          else:
              self.wv = KeyedVectors.load_word2vec_format(word2vec_path, binary=binary)
  
      def score(self, topk=10, binary= False):
          """
          :param topk: how many most likely words to consider in the evaluation
          :return: topic coherence computed on the word embeddings similarities
          """
          if topk > len(self.topics[0]):
              raise Exception('Words in topics are less than topk')
          else:
              arrays = []
              for index, topic in enumerate(self.topics):
                  if len(topic) > 0:
                      local_simi = []
                      for word1, word2 in itertools.combinations(topic[0:topk], 2):
                          if word1 in self.wv.vocab and word2 in self.wv.vocab:
                              local_simi.append(self.wv.similarity(word1, word2))
                      arrays.append(np.mean(local_simi))
              return np.mean(arrays)
  
  
  class InvertedRBO(Measure):
      def __init__(self, topics):
          """
          :param topics: a list of lists of words
          """
          super().__init__()
          self.topics = topics
  
      def score(self, topk = 10, weight=0.9):
          """
          :param weight: p (float), default 1.0: Weight of each agreement at depth d:
          p**(d-1). When set to 1.0, there is no weight, the rbo returns to average overlap.
          :return: rank_biased_overlap over the topics
          """
          if topk > len(self.topics[0]):
              raise Exception('Words in topics are less than topk')
          else:
              collect = []
              for list1, list2 in itertools.combinations(self.topics, 2):
                  rbo_val = rbo.rbo(list1[:topk], list2[:topk], p=weight)[2]
                  collect.append(rbo_val)
              return 1 - np.mean(collect)
  
  
  class Matches(Measure):
      def __init__(self, doc_distribution_original_language, doc_distribution_unseen_language):
          """
           :param doc_distribution_original_language: numpy array of the topical distribution of
           the documents in the original language (dim: num docs x num topics)
           :param doc_distribution_unseen_language: numpy array of the topical distribution of the
            documents in an unseen language (dim: num docs x num topics)
           """
          super().__init__()
          self.orig_lang_docs = doc_distribution_original_language
          self.unseen_lang_docs = doc_distribution_unseen_language
          if len(self.orig_lang_docs) != len(self.unseen_lang_docs):
              raise Exception('Distributions of the comparable documents must have the same length')
  
      def score(self):
          """
          :return: proportion of matches between the predicted topic in the original language and
          the predicted topic in the unseen language of the document distributions
          """
          matches = 0
          for d1, d2 in zip(self.orig_lang_docs, self.unseen_lang_docs):
              if np.argmax(d1) == np.argmax(d2):
                  matches = matches + 1
          return matches/len(self.unseen_lang_docs)
  
  
  class KLDivergence(Measure):
      def __init__(self, doc_distribution_original_language, doc_distribution_unseen_language):
          """
           :param doc_distribution_original_language: numpy array of the topical distribution of
           the documents in the original language (dim: num docs x num topics)
           :param doc_distribution_unseen_language: numpy array of the topical distribution of the
            documents in an unseen language (dim: num docs x num topics)
           """
          super().__init__()
          self.orig_lang_docs = doc_distribution_original_language
          self.unseen_lang_docs = doc_distribution_unseen_language
          if len(self.orig_lang_docs) != len(self.unseen_lang_docs):
              raise Exception('Distributions of the comparable documents must have the same length')
  
      def score(self):
          """
          :return: average kullback leibler divergence between the distributions
          """
          kl_mean = 0
          for d1, d2 in zip(self.orig_lang_docs, self.unseen_lang_docs):
              kl_mean = kl_mean + kl_div(d1, d2)
          return kl_mean/len(self.unseen_lang_docs)
  
  
  def kl_div(a, b):
      a = np.asarray(a, dtype=np.float)
      b = np.asarray(b, dtype=np.float)
      return np.sum(np.where(a != 0, a * np.log(a / b), 0))
  
  
  class CentroidDistance(Measure):
      def __init__(self, doc_distribution_original_language, doc_distribution_unseen_language, topics, word2vec_path=None,
                   binary=True, topk=10):
          """
           :param doc_distribution_original_language: numpy array of the topical distribution of the
           documents in the original language (dim: num docs x num topics)
           :param doc_distribution_unseen_language: numpy array of the topical distribution of the
           documents in an unseen language (dim: num docs x num topics)
           :param topics: a list of lists of the top-n most likely words
           :param word2vec_path: if word2vec_file is specified, it retrieves the word embeddings
           file (in word2vec format) to compute similarities between words, otherwise
           'word2vec-google-news-300' is downloaded
           :param binary: if the word2vec file is binary
           :param topk: max number of topical words
           """
          super().__init__()
          self.topics = [t[:topk] for t in topics]
          self.orig_lang_docs = doc_distribution_original_language
          self.unseen_lang_docs = doc_distribution_unseen_language
          if len(self.orig_lang_docs) != len(self.unseen_lang_docs):
              raise Exception('Distributions of the comparable documents must have the same length')
  
          if word2vec_path is None:
              self.wv = api.load('word2vec-google-news-300')
          else:
              self.wv = KeyedVectors.load_word2vec_format(word2vec_path, binary=binary)
  
      def score(self):
          """
          :return: average centroid distance between the words of the most likely topic of the
          document distributions
          """
          cd = 0
          for d1, d2 in zip(self.orig_lang_docs, self.unseen_lang_docs):
              top_words_orig = self.topics[np.argmax(d1)]
              top_words_unseen = self.topics[np.argmax(d2)]
  
              centroid_lang = self.get_centroid(top_words_orig)
              centroid_en = self.get_centroid(top_words_unseen)
  
              cd += (1 - cosine(centroid_lang, centroid_en))
          return cd/len(self.unseen_lang_docs)
  
      def get_centroid(self, word_list):
          vector_list = []
          for word in word_list:
              if word in self.wv.vocab:
                  vector_list.append(self.wv.get_vector(word))
          vec = sum(vector_list)
          return vec / np.linalg.norm(vec)
  
  
apis: numpy.asarray numpy.argmax numpy.log numpy.linalg.norm scipy.spatial.distance.cosine numpy.mean