{"action":"created","comment":{"url":"https://api.github.com/repos/RaRe-Technologies/gensim/pulls/comments/94289737","pull_request_review_id":14817933,"id":94289737,"diff_hunk":"@@ -0,0 +1,1017 @@\n+#!/usr/bin/env python\n+# -*- coding: utf-8 -*-\n+#\n+# Copyright (C) 2016 Radim Rehurek <radimrehurek@seznam.cz>\n+# Copyright (C) 2016 Olavur Mortensen <olavurmortensen@gmail.com>\n+# Licensed under the GNU LGPL v2.1 - http://www.gnu.org/licenses/lgpl.html\n+\n+\n+\"\"\"\n+Author-topic model in Python.\n+\n+This module trains the author-topic model on documents and corresponding author-document\n+dictionaries. The training is online and is constant in memory w.r.t. the number of \n+documents. The model is *not* constant in memory w.r.t. the number of authors.\n+\n+The model can be updated with additional documents after taining has been completed. It is\n+also possible to continue training on the existing data.\n+\n+The model is closely related to Latent Dirichlet Allocation. Usage of the AuthorTopicModel\n+class is likewise similar to the usage of the LdaModel class.\n+\n+\"\"\"\n+\n+import pdb\n+from pdb import set_trace as st\n+from pprint import pprint\n+\n+import logging\n+import numpy as np # for arrays, array broadcasting etc.\n+import numbers\n+from copy import deepcopy\n+from shutil import copyfile\n+from os.path import isfile\n+from os import remove\n+\n+from gensim import utils\n+from gensim.models import LdaModel\n+from gensim.models.ldamodel import dirichlet_expectation, get_random_state, LdaState\n+from gensim.corpora import MmCorpus\n+from itertools import chain\n+from scipy.special import gammaln  # gamma function utils\n+from six.moves import xrange\n+import six\n+\n+logger = logging.getLogger('gensim.models.atmodel')\n+\n+class AuthorTopicState(LdaState):\n+    \"\"\"\n+    NOTE: distributed mode not available yet in the author-topic model. This AuthorTopicState\n+    object is kept so that when the time comes to imlement it, it will be easier.\n+\n+    Encapsulate information for distributed computation of AuthorTopicModel objects.\n+\n+    Objects of this class are sent over the network, so try to keep them lean to\n+    reduce traffic.\n+\n+    \"\"\"\n+    def __init__(self, eta, lambda_shape, gamma_shape):\n+        self.eta = eta\n+        self.sstats = np.zeros(lambda_shape)\n+        self.gamma = np.zeros(gamma_shape)\n+        self.numdocs = 0\n+\n+def construct_doc2author(corpus, author2doc):\n+    \"\"\"Make a mapping from document IDs to author IDs.\"\"\"\n+    doc2author = {}\n+    for d, _ in enumerate(corpus):\n+        author_ids = []\n+        for a, a_doc_ids in author2doc.items():\n+            if d in a_doc_ids:\n+                author_ids.append(a)\n+        doc2author[d] = author_ids\n+    return doc2author\n+\n+def construct_author2doc(corpus, doc2author):\n+    \"\"\"Make a mapping from author IDs to document IDs.\"\"\"\n+\n+    # First get a set of all authors.\n+    authors_ids = set()\n+    for d, a_doc_ids in doc2author.items():\n+        for a in a_doc_ids:\n+            authors_ids.add(a)\n+\n+    # Now construct the dictionary.\n+    author2doc = {}\n+    for a in authors_ids:\n+        author2doc[a] = []\n+        for d, a_ids in doc2author.items():\n+            if a in a_ids:\n+                author2doc[a].append(d)\n+    return author2doc\n+\n+class AuthorTopicModel(LdaModel):\n+    \"\"\"\n+    The constructor estimates the author-topic model parameters based\n+    on a training corpus:\n+\n+    >>> model = AuthorTopicModel(corpus, num_topics=10, author2doc=author2doc, id2word=id2word)\n+\n+    The model can be updated (trained) with new documents via\n+\n+    >>> model.update(other_corpus, other_author2doc)\n+\n+    Model persistency is achieved through its `load`/`save` methods.\n+    \"\"\"\n+\n+    def __init__(self, corpus=None, num_topics=100, id2word=None, author2doc=None, doc2author=None,\n+                 chunksize=2000, passes=1, iterations=50, decay=0.5, offset=1.0,\n+                 alpha='symmetric', eta='symmetric', update_every=1, eval_every=10, \n+                 gamma_threshold=0.001, serialized=False, serialization_path=None,\n+                 minimum_probability=0.01, random_state=None, var_lambda=None,\n+                 ns_conf={}):\n+        \"\"\"\n+        If the iterable corpus and one of author2doc/doc2author dictionaries are given,\n+        start training straight away. If not given, the model is left untrained \n+        (presumably because you want to call `update()` manually).\n+\n+        `num_topics` is the number of requested latent topics to be extracted from\n+        the training corpus.\n+\n+        `id2word` is a mapping from word ids (integers) to words (strings). It is\n+        used to determine the vocabulary size, as well as for debugging and topic\n+        printing.\n+\n+        `author2doc` is a dictionary where the keys are the names of authors, and the\n+        values are lists of documents that the author contributes to.\n+\n+        `doc2author` is a dictionary where the keys are document IDs (indexes to corpus)\n+        and the values are lists of author names. I.e. this is the reverse mapping of\n+        `author2doc`. Only one of the two, `author2doc` and `doc2author` have to be\n+        supplied.\n+\n+        `passes` is the number of times the model makes a pass over the entire trianing\n+        data.\n+\n+        `iterations` is the maximum number of times the model loops over each document \n+        (M-step). The iterations stop when convergence is reached.\n+\n+        `chunksize` controls the size of the mini-batches.\n+\n+        `alpha` and `eta` are hyperparameters that affect sparsity of the author-topic\n+        (theta) and topic-word (lambda) distributions. Both default to a symmetric\n+        1.0/num_topics prior.\n+\n+        `alpha` can be set to an explicit array = prior of your choice. It also\n+        support special values of 'asymmetric' and 'auto': the former uses a fixed\n+        normalized asymmetric 1.0/topicno prior, the latter learns an asymmetric\n+        prior directly from your data.\n+\n+        `eta` can be a scalar for a symmetric prior over topic/word\n+        distributions, or a vector of shape num_words, which can be used to \n+        impose (user defined) asymmetric priors over the word distribution. \n+        It also supports the special value 'auto', which learns an asymmetric\n+        prior over words directly from your data. `eta` can also be a matrix\n+        of shape num_topics x num_words, which can be used to impose \n+        asymmetric priors over the word distribution on a per-topic basis\n+        (can not be learned from data).\n+\n+        Calculate and log perplexity estimate from the latest mini-batch every\n+        `eval_every` model updates. Set to None to disable perplexity estimation.\n+\n+        `decay` and `offset` parameters are the same as Kappa and Tau_0 in\n+        Hoffman et al, respectively. `decay` controls how quickly old documents are\n+        forgotten, while `offset` down-weights early iterations.\n+\n+        `minimum_probability` controls filtering the topics returned for a document (bow).\n+\n+        `random_state` can be an integer or a numpy.random.RandomState object. Set the \n+        state of the random number generator inside the author-topic model, to ensure\n+        reproducibility of your experiments, for example.\n+\n+        `serialized` indicates whether the input corpora to the model are simple\n+        in-memory lists (`serialized = False`) or saved to the hard-drive \n+        (`serialized = True`). Note that this behaviour is quite different from\n+        other Gensim models. If your data is too large to fit in to memory, use\n+        this functionality. Note that calling `AuthorTopicModel.update` with new \n+        data may be cumbersome as it requires all the existing data to be \n+        re-serialized.\n+\n+        `serialization_path` must be set to a filepath, if `serialized = True` is\n+        used. Use, for example, `serialization_path = /tmp/serialized_model.mm` or use your\n+        working directory by setting `serialization_path = serialized_model.mm`. An existing\n+        file *cannot* be overwritten; either delete the old file or choose a different\n+        name. \n+\n+        Example:\n+\n+        >>> model = AuthorTopicModel(corpus, num_topics=100, author2doc=author2doc, id2word=id2word)  # train model\n+        >>> model.update(corpus2) # update the author-topic model with additional documents\n+\n+        >>> model = AuthorTopicModel(corpus, num_topics=50, author2doc=author2doc, id2word=id2word, alpha='auto', eval_every=5)  # train asymmetric alpha from data\n+\n+        \"\"\"\n+\n+        # NOTE: as distributed version of this model is not implemented, \"distributed\" is set to false. Some of the\n+        # infrastructure to implement a distributed author-topic model is already in place, such as the AuthorTopicState.\n+        # As the \"ns_conf\" input variable is only used in the distributed version, it is simply ignored here.\n+        distributed = False\n+\n+        self.id2word = id2word\n+        if corpus is None and self.id2word is None:\n+            raise ValueError('at least one of corpus/id2word must be specified, to establish input space dimensionality')\n+\n+        if self.id2word is None:\n+            logger.warning(\"no word id mapping provided; initializing from corpus, assuming identity\")\n+            self.id2word = utils.dict_from_corpus(corpus)\n+            self.num_terms = len(self.id2word)\n+        elif len(self.id2word) > 0:\n+            self.num_terms = 1 + max(self.id2word.keys())\n+        else:\n+            self.num_terms = 0\n+\n+        if self.num_terms == 0:\n+            raise ValueError(\"cannot compute the author-topic model over an empty collection (no terms)\")\n+\n+        logger.info('Vocabulary consists of %d words.', self.num_terms)\n+\n+        self.author2doc = {}\n+        self.doc2author = {}\n+\n+        self.distributed = distributed\n+        self.num_topics = num_topics\n+        self.num_authors = 0\n+        self.chunksize = chunksize\n+        self.decay = decay\n+        self.offset = offset\n+        self.minimum_probability = minimum_probability \n+        self.num_updates = 0\n+        self.total_docs = 0\n+\n+        self.passes = passes\n+        self.update_every = update_every\n+        self.eval_every = eval_every\n+\n+        self.author2id = {}\n+        self.id2author = {}\n+\n+        self.serialized = serialized\n+        if serialized and not serialization_path:\n+            raise ValueError(\"If serialized corpora are used, a the path to a folder where the corpus should be saved must be provided (serialized_path).\")\n+        if serialized and serialization_path:\n+            assert not isfile(serialization_path), \"A file already exists at the serialization_path path; choose a different serialization_path, or delete the file.\"\n+        self.serialization_path = serialization_path\n+\n+        # Initialize an empty self.corpus.\n+        self.init_empty_corpus()\n+\n+        self.alpha, self.optimize_alpha = self.init_dir_prior(alpha, 'alpha')\n+\n+        assert self.alpha.shape == (self.num_topics,), \"Invalid alpha shape. Got shape %s, but expected (%d, )\" % (str(self.alpha.shape), self.num_topics)\n+\n+        if isinstance(eta, six.string_types):\n+            if eta == 'asymmetric':\n+                raise ValueError(\"The 'asymmetric' option cannot be used for eta\")\n+\n+        self.eta, self.optimize_eta = self.init_dir_prior(eta, 'eta')\n+\n+        self.random_state = get_random_state(random_state)\n+\n+        assert (self.eta.shape == (self.num_terms,) or self.eta.shape == (self.num_topics, self.num_terms)), (\n+                \"Invalid eta shape. Got shape %s, but expected (%d, 1) or (%d, %d)\" %\n+                (str(self.eta.shape), self.num_terms, self.num_topics, self.num_terms))\n+\n+        if not distributed:\n+            self.dispatcher = None\n+            self.numworkers = 1\n+        else:\n+            # NOTE: distributed processing is not implemented for the author-topic model.\n+            pass\n+\n+        # VB constants\n+        self.iterations = iterations\n+        self.gamma_threshold = gamma_threshold\n+\n+        # Initialize the variational distributions q(beta|lambda) and q(theta|gamma)\n+        self.state = AuthorTopicState(self.eta, (self.num_topics, self.num_terms), (self.num_authors, self.num_topics))\n+        self.state.sstats = self.random_state.gamma(100., 1. / 100., (self.num_topics, self.num_terms))\n+        self.expElogbeta = np.exp(dirichlet_expectation(self.state.sstats))\n+\n+        # if a training corpus was provided, start estimating the model right away\n+        if corpus is not None and (author2doc is not None or doc2author is not None):\n+            use_numpy = self.dispatcher is not None\n+            self.update(corpus, author2doc, doc2author, chunks_as_numpy=use_numpy)\n+\n+    def __str__(self):\n+        return \"AuthorTopicModel(num_terms=%s, num_topics=%s, num_authors=%s, decay=%s, chunksize=%s)\" % \\\n+            (self.num_terms, self.num_topics, self.num_authors, self.decay, self.chunksize)\n+\n+    def init_empty_corpus(self):\n+        \"\"\"\n+        Initialize an empty corpus. If the corpora are to be treated as lists, simply\n+        initialize an empty list. If serialization is used, initialize an empty corpus\n+        of the class `gensim.corpora.MmCorpus`.\n+\n+        \"\"\"\n+        if self.serialized:\n+            # Tnitialize the corpus as a serialized empty list.\n+            # This corpus will be extended in self.update.\n+            MmCorpus.serialize(self.serialization_path, [])  # Serialize empty corpus.\n+            self.corpus = MmCorpus(self.serialization_path)  # Store serialized corpus object in self.corpus.\n+        else:\n+            # All input corpora are assumed to just be lists.\n+            self.corpus = []\n+\n+    def extend_corpus(self, corpus):\n+        \"\"\"\n+        Add new documents in `corpus` to `self.corpus`. If serialization is used,\n+        then the entire corpus (`self.corpus`) is re-serialized and the new documents\n+        are added in the process. If serialization is not used, the corpus, as a list\n+        of documents, is simply extended.\n+        \n+        \"\"\"\n+        if self.serialized:\n+            # Re-serialize the entire corpus while appending the new documents.\n+            if isinstance(corpus, MmCorpus):\n+                # Check that we are not attempting to overwrite the serialized corpus.\n+                assert self.corpus.input != corpus.input, 'Input corpus cannot have the same file path as the model corpus (serialization_path).'\n+            corpus_chain = chain(self.corpus, corpus)  # A generator with the old and new documents.\n+            copyfile(self.serialization_path, self.serialization_path + '.tmp')  # Make a temporary copy of the file where the corpus is serialized.\n+            self.corpus.input = self.serialization_path + '.tmp'  # Point the old corpus at this temporary file.\n+            MmCorpus.serialize(self.serialization_path, corpus_chain)  # Re-serialize the old corpus, and extend it with the new corpus.\n+            self.corpus = MmCorpus(self.serialization_path)  # Store the new serialized corpus object in self.corpus.\n+            remove(self.serialization_path + '.tmp')  # Remove the temporary file again.\n+        else:\n+            # self.corpus and corpus are just lists, just extend the list.\n+            # First check that corpus is actually a list.\n+            assert isinstance(corpus, list), \"If serialized == False, all input corpora must be lists.\"\n+            self.corpus.extend(corpus)\n+\n+\n+    def compute_phinorm(self, ids, authors_d, expElogthetad, expElogbetad):\n+        \"\"\"Efficiently computes the normalizing factor in phi.\"\"\"\n+        phinorm = np.zeros(len(ids))\n+        expElogtheta_sum = np.zeros(self.num_topics)\n+        for a in xrange(len(authors_d)):\n+            expElogtheta_sum += expElogthetad[a, :]\n+        phinorm = expElogtheta_sum.dot(expElogbetad)\n+\n+        return phinorm\n+\n+    def inference(self, chunk, author2doc, doc2author, rhot, collect_sstats=False, chunk_doc_idx=None):\n+        \"\"\"\n+        Given a chunk of sparse document vectors, update gamma (parameters\n+        controlling the topic weights) for each author corresponding to the\n+        documents in the chunk.\n+\n+        The whole input chunk of document is assumed to fit in RAM; chunking of\n+        a large corpus must be done earlier in the pipeline.\n+\n+        If `collect_sstats` is True, also collect sufficient statistics needed\n+        to update the model's topic-word distributions, and return a 2-tuple\n+        `(gamma_chunk, sstats)`. Otherwise, return `(gamma_chunk, None)`. \n+        `gamma_cunk` is of shape `len(chunk_authors) x self.num_topics`, where \n+        `chunk_authors` is the number of authors in the documents in the\n+        current chunk.\n+\n+        Avoids computing the `phi` variational parameter directly using the\n+        optimization presented in **Lee, Seung: Algorithms for non-negative matrix factorization, NIPS 2001**.\n+\n+        \"\"\"\n+        try:\n+            _ = len(chunk)\n+        except:\n+            # convert iterators/generators to plain list, so we have len() etc.\n+            chunk = list(chunk)\n+        if len(chunk) > 1:\n+            logger.debug(\"performing inference on a chunk of %i documents\", len(chunk))\n+\n+        # Initialize the variational distribution q(theta|gamma) for the chunk\n+        if collect_sstats:\n+            sstats = np.zeros_like(self.expElogbeta)\n+        else:\n+            sstats = None\n+        converged = 0\n+\n+        # Stack all the computed gammas into this output array.\n+        gamma_chunk = np.zeros((0, self.num_topics))\n+\n+        # Now, for each document d update gamma and phi w.r.t. all authors in those documents.\n+        for d, doc in enumerate(chunk):\n+            if chunk_doc_idx is not None:\n+                doc_no = chunk_doc_idx[d]\n+            else:\n+                doc_no = d\n+            # Get the IDs and counts of all the words in the current document.\n+            if doc and not isinstance(doc[0][0], six.integer_types):\n+                # make sure the term IDs are ints, otherwise np will get upset\n+                ids = [int(id) for id, _ in doc]\n+            else:\n+                ids = [id for id, _ in doc]\n+            cts = np.array([cnt for _, cnt in doc])","path":"gensim/models/atmodel.py","position":391,"original_position":391,"commit_id":"ac9ecd4ec7ba4bfbe78ae92a7d5d3d5a746d8837","original_commit_id":"ac9ecd4ec7ba4bfbe78ae92a7d5d3d5a746d8837","user":{"login":"tmylk","id":1261528,"avatar_url":"https://avatars.githubusercontent.com/u/1261528?v=3","gravatar_id":"","url":"https://api.github.com/users/tmylk","html_url":"https://github.com/tmylk","followers_url":"https://api.github.com/users/tmylk/followers","following_url":"https://api.github.com/users/tmylk/following{/other_user}","gists_url":"https://api.github.com/users/tmylk/gists{/gist_id}","starred_url":"https://api.github.com/users/tmylk/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/tmylk/subscriptions","organizations_url":"https://api.github.com/users/tmylk/orgs","repos_url":"https://api.github.com/users/tmylk/repos","events_url":"https://api.github.com/users/tmylk/events{/privacy}","received_events_url":"https://api.github.com/users/tmylk/received_events","type":"User","site_admin":false},"body":"this is duplicate code with ldamodel. do you have plans to refactor to have less code?","created_at":"2017-01-01T22:25:59Z","updated_at":"2017-01-01T22:59:44Z","html_url":"https://github.com/RaRe-Technologies/gensim/pull/893#discussion_r94289737","pull_request_url":"https://api.github.com/repos/RaRe-Technologies/gensim/pulls/893","_links":{"self":{"href":"https://api.github.com/repos/RaRe-Technologies/gensim/pulls/comments/94289737"},"html":{"href":"https://github.com/RaRe-Technologies/gensim/pull/893#discussion_r94289737"},"pull_request":{"href":"https://api.github.com/repos/RaRe-Technologies/gensim/pulls/893"}}},"pull_request":{"url":"https://api.github.com/repos/RaRe-Technologies/gensim/pulls/893","id":86918381,"html_url":"https://github.com/RaRe-Technologies/gensim/pull/893","diff_url":"https://github.com/RaRe-Technologies/gensim/pull/893.diff","patch_url":"https://github.com/RaRe-Technologies/gensim/pull/893.patch","issue_url":"https://api.github.com/repos/RaRe-Technologies/gensim/issues/893","number":893,"state":"open","locked":false,"title":"[WIP] Author-topic model ","user":{"login":"olavurmortensen","id":2675757,"avatar_url":"https://avatars.githubusercontent.com/u/2675757?v=3","gravatar_id":"","url":"https://api.github.com/users/olavurmortensen","html_url":"https://github.com/olavurmortensen","followers_url":"https://api.github.com/users/olavurmortensen/followers","following_url":"https://api.github.com/users/olavurmortensen/following{/other_user}","gists_url":"https://api.github.com/users/olavurmortensen/gists{/gist_id}","starred_url":"https://api.github.com/users/olavurmortensen/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/olavurmortensen/subscriptions","organizations_url":"https://api.github.com/users/olavurmortensen/orgs","repos_url":"https://api.github.com/users/olavurmortensen/repos","events_url":"https://api.github.com/users/olavurmortensen/events{/privacy}","received_events_url":"https://api.github.com/users/olavurmortensen/received_events","type":"User","site_admin":false},"body":"@tmylk @piskvorky \n\nI'm implementing the author-topic (AT) model, which is based on \"[The Author-Topic Model for Authors and Documents](https://mimno.infosci.cornell.edu/info6150/readings/398.pdf)\" by Rosen-Zvi and co-authors. This project is in connection with my masters thesis at the Technical University of Denmark.\n\nAs indicated in the PR title, this is a work in progress. \n\nThe original paper presents an algorithm based on collapsed Gibbs sampling. I have derived a variational Bayes algorithm to trained this model instead, and implemented that algorithm. Furthermore, I have made an online algorithm using the method described in \"[Online Learning For Latent Dirichlet Allocation](https://www.cs.princeton.edu/~blei/papers/HoffmanBleiBach2010b.pdf)\" by Hoffman and co-authors.\n\nAt the moment, the algorithm runs and seems to converge. I intend to run some experiments to see if the resulting topics are good soon. I expect to find that I need to do some improvements, but we will see.\n","created_at":"2016-09-27T12:20:03Z","updated_at":"2017-01-01T22:59:44Z","closed_at":null,"merged_at":null,"merge_commit_sha":"ab3312e9588c1dc816f1b1d202e7e4e7f366afd3","assignee":{"login":"tmylk","id":1261528,"avatar_url":"https://avatars.githubusercontent.com/u/1261528?v=3","gravatar_id":"","url":"https://api.github.com/users/tmylk","html_url":"https://github.com/tmylk","followers_url":"https://api.github.com/users/tmylk/followers","following_url":"https://api.github.com/users/tmylk/following{/other_user}","gists_url":"https://api.github.com/users/tmylk/gists{/gist_id}","starred_url":"https://api.github.com/users/tmylk/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/tmylk/subscriptions","organizations_url":"https://api.github.com/users/tmylk/orgs","repos_url":"https://api.github.com/users/tmylk/repos","events_url":"https://api.github.com/users/tmylk/events{/privacy}","received_events_url":"https://api.github.com/users/tmylk/received_events","type":"User","site_admin":false},"assignees":[{"login":"tmylk","id":1261528,"avatar_url":"https://avatars.githubusercontent.com/u/1261528?v=3","gravatar_id":"","url":"https://api.github.com/users/tmylk","html_url":"https://github.com/tmylk","followers_url":"https://api.github.com/users/tmylk/followers","following_url":"https://api.github.com/users/tmylk/following{/other_user}","gists_url":"https://api.github.com/users/tmylk/gists{/gist_id}","starred_url":"https://api.github.com/users/tmylk/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/tmylk/subscriptions","organizations_url":"https://api.github.com/users/tmylk/orgs","repos_url":"https://api.github.com/users/tmylk/repos","events_url":"https://api.github.com/users/tmylk/events{/privacy}","received_events_url":"https://api.github.com/users/tmylk/received_events","type":"User","site_admin":false}],"milestone":null,"commits_url":"https://api.github.com/repos/RaRe-Technologies/gensim/pulls/893/commits","review_comments_url":"https://api.github.com/repos/RaRe-Technologies/gensim/pulls/893/comments","review_comment_url":"https://api.github.com/repos/RaRe-Technologies/gensim/pulls/comments{/number}","comments_url":"https://api.github.com/repos/RaRe-Technologies/gensim/issues/893/comments","statuses_url":"https://api.github.com/repos/RaRe-Technologies/gensim/statuses/ac9ecd4ec7ba4bfbe78ae92a7d5d3d5a746d8837","head":{"label":"olavurmortensen:author-topic_model","ref":"author-topic_model","sha":"ac9ecd4ec7ba4bfbe78ae92a7d5d3d5a746d8837","user":{"login":"olavurmortensen","id":2675757,"avatar_url":"https://avatars.githubusercontent.com/u/2675757?v=3","gravatar_id":"","url":"https://api.github.com/users/olavurmortensen","html_url":"https://github.com/olavurmortensen","followers_url":"https://api.github.com/users/olavurmortensen/followers","following_url":"https://api.github.com/users/olavurmortensen/following{/other_user}","gists_url":"https://api.github.com/users/olavurmortensen/gists{/gist_id}","starred_url":"https://api.github.com/users/olavurmortensen/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/olavurmortensen/subscriptions","organizations_url":"https://api.github.com/users/olavurmortensen/orgs","repos_url":"https://api.github.com/users/olavurmortensen/repos","events_url":"https://api.github.com/users/olavurmortensen/events{/privacy}","received_events_url":"https://api.github.com/users/olavurmortensen/received_events","type":"User","site_admin":false},"repo":{"id":40701336,"name":"gensim","full_name":"olavurmortensen/gensim","owner":{"login":"olavurmortensen","id":2675757,"avatar_url":"https://avatars.githubusercontent.com/u/2675757?v=3","gravatar_id":"","url":"https://api.github.com/users/olavurmortensen","html_url":"https://github.com/olavurmortensen","followers_url":"https://api.github.com/users/olavurmortensen/followers","following_url":"https://api.github.com/users/olavurmortensen/following{/other_user}","gists_url":"https://api.github.com/users/olavurmortensen/gists{/gist_id}","starred_url":"https://api.github.com/users/olavurmortensen/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/olavurmortensen/subscriptions","organizations_url":"https://api.github.com/users/olavurmortensen/orgs","repos_url":"https://api.github.com/users/olavurmortensen/repos","events_url":"https://api.github.com/users/olavurmortensen/events{/privacy}","received_events_url":"https://api.github.com/users/olavurmortensen/received_events","type":"User","site_admin":false},"private":false,"html_url":"https://github.com/olavurmortensen/gensim","description":"Topic Modelling for Humans","fork":true,"url":"https://api.github.com/repos/olavurmortensen/gensim","forks_url":"https://api.github.com/repos/olavurmortensen/gensim/forks","keys_url":"https://api.github.com/repos/olavurmortensen/gensim/keys{/key_id}","collaborators_url":"https://api.github.com/repos/olavurmortensen/gensim/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/olavurmortensen/gensim/teams","hooks_url":"https://api.github.com/repos/olavurmortensen/gensim/hooks","issue_events_url":"https://api.github.com/repos/olavurmortensen/gensim/issues/events{/number}","events_url":"https://api.github.com/repos/olavurmortensen/gensim/events","assignees_url":"https://api.github.com/repos/olavurmortensen/gensim/assignees{/user}","branches_url":"https://api.github.com/repos/olavurmortensen/gensim/branches{/branch}","tags_url":"https://api.github.com/repos/olavurmortensen/gensim/tags","blobs_url":"https://api.github.com/repos/olavurmortensen/gensim/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/olavurmortensen/gensim/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/olavurmortensen/gensim/git/refs{/sha}","trees_url":"https://api.github.com/repos/olavurmortensen/gensim/git/trees{/sha}","statuses_url":"https://api.github.com/repos/olavurmortensen/gensim/statuses/{sha}","languages_url":"https://api.github.com/repos/olavurmortensen/gensim/languages","stargazers_url":"https://api.github.com/repos/olavurmortensen/gensim/stargazers","contributors_url":"https://api.github.com/repos/olavurmortensen/gensim/contributors","subscribers_url":"https://api.github.com/repos/olavurmortensen/gensim/subscribers","subscription_url":"https://api.github.com/repos/olavurmortensen/gensim/subscription","commits_url":"https://api.github.com/repos/olavurmortensen/gensim/commits{/sha}","git_commits_url":"https://api.github.com/repos/olavurmortensen/gensim/git/commits{/sha}","comments_url":"https://api.github.com/repos/olavurmortensen/gensim/comments{/number}","issue_comment_url":"https://api.github.com/repos/olavurmortensen/gensim/issues/comments{/number}","contents_url":"https://api.github.com/repos/olavurmortensen/gensim/contents/{+path}","compare_url":"https://api.github.com/repos/olavurmortensen/gensim/compare/{base}...{head}","merges_url":"https://api.github.com/repos/olavurmortensen/gensim/merges","archive_url":"https://api.github.com/repos/olavurmortensen/gensim/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/olavurmortensen/gensim/downloads","issues_url":"https://api.github.com/repos/olavurmortensen/gensim/issues{/number}","pulls_url":"https://api.github.com/repos/olavurmortensen/gensim/pulls{/number}","milestones_url":"https://api.github.com/repos/olavurmortensen/gensim/milestones{/number}","notifications_url":"https://api.github.com/repos/olavurmortensen/gensim/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/olavurmortensen/gensim/labels{/name}","releases_url":"https://api.github.com/repos/olavurmortensen/gensim/releases{/id}","deployments_url":"https://api.github.com/repos/olavurmortensen/gensim/deployments","created_at":"2015-08-14T07:15:46Z","updated_at":"2016-02-28T11:40:26Z","pushed_at":"2016-12-28T13:21:54Z","git_url":"git://github.com/olavurmortensen/gensim.git","ssh_url":"git@github.com:olavurmortensen/gensim.git","clone_url":"https://github.com/olavurmortensen/gensim.git","svn_url":"https://github.com/olavurmortensen/gensim","homepage":"http://radimrehurek.com/gensim/","size":22708,"stargazers_count":0,"watchers_count":0,"language":"Python","has_issues":false,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":0,"mirror_url":null,"open_issues_count":0,"forks":0,"open_issues":0,"watchers":0,"default_branch":"develop"}},"base":{"label":"RaRe-Technologies:develop","ref":"develop","sha":"54871ba162edb4726b9a2b35b10f947c0dfdda1f","user":{"login":"RaRe-Technologies","id":12515886,"avatar_url":"https://avatars.githubusercontent.com/u/12515886?v=3","gravatar_id":"","url":"https://api.github.com/users/RaRe-Technologies","html_url":"https://github.com/RaRe-Technologies","followers_url":"https://api.github.com/users/RaRe-Technologies/followers","following_url":"https://api.github.com/users/RaRe-Technologies/following{/other_user}","gists_url":"https://api.github.com/users/RaRe-Technologies/gists{/gist_id}","starred_url":"https://api.github.com/users/RaRe-Technologies/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/RaRe-Technologies/subscriptions","organizations_url":"https://api.github.com/users/RaRe-Technologies/orgs","repos_url":"https://api.github.com/users/RaRe-Technologies/repos","events_url":"https://api.github.com/users/RaRe-Technologies/events{/privacy}","received_events_url":"https://api.github.com/users/RaRe-Technologies/received_events","type":"Organization","site_admin":false},"repo":{"id":1349775,"name":"gensim","full_name":"RaRe-Technologies/gensim","owner":{"login":"RaRe-Technologies","id":12515886,"avatar_url":"https://avatars.githubusercontent.com/u/12515886?v=3","gravatar_id":"","url":"https://api.github.com/users/RaRe-Technologies","html_url":"https://github.com/RaRe-Technologies","followers_url":"https://api.github.com/users/RaRe-Technologies/followers","following_url":"https://api.github.com/users/RaRe-Technologies/following{/other_user}","gists_url":"https://api.github.com/users/RaRe-Technologies/gists{/gist_id}","starred_url":"https://api.github.com/users/RaRe-Technologies/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/RaRe-Technologies/subscriptions","organizations_url":"https://api.github.com/users/RaRe-Technologies/orgs","repos_url":"https://api.github.com/users/RaRe-Technologies/repos","events_url":"https://api.github.com/users/RaRe-Technologies/events{/privacy}","received_events_url":"https://api.github.com/users/RaRe-Technologies/received_events","type":"Organization","site_admin":false},"private":false,"html_url":"https://github.com/RaRe-Technologies/gensim","description":"Topic Modelling for Humans","fork":false,"url":"https://api.github.com/repos/RaRe-Technologies/gensim","forks_url":"https://api.github.com/repos/RaRe-Technologies/gensim/forks","keys_url":"https://api.github.com/repos/RaRe-Technologies/gensim/keys{/key_id}","collaborators_url":"https://api.github.com/repos/RaRe-Technologies/gensim/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/RaRe-Technologies/gensim/teams","hooks_url":"https://api.github.com/repos/RaRe-Technologies/gensim/hooks","issue_events_url":"https://api.github.com/repos/RaRe-Technologies/gensim/issues/events{/number}","events_url":"https://api.github.com/repos/RaRe-Technologies/gensim/events","assignees_url":"https://api.github.com/repos/RaRe-Technologies/gensim/assignees{/user}","branches_url":"https://api.github.com/repos/RaRe-Technologies/gensim/branches{/branch}","tags_url":"https://api.github.com/repos/RaRe-Technologies/gensim/tags","blobs_url":"https://api.github.com/repos/RaRe-Technologies/gensim/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/RaRe-Technologies/gensim/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/RaRe-Technologies/gensim/git/refs{/sha}","trees_url":"https://api.github.com/repos/RaRe-Technologies/gensim/git/trees{/sha}","statuses_url":"https://api.github.com/repos/RaRe-Technologies/gensim/statuses/{sha}","languages_url":"https://api.github.com/repos/RaRe-Technologies/gensim/languages","stargazers_url":"https://api.github.com/repos/RaRe-Technologies/gensim/stargazers","contributors_url":"https://api.github.com/repos/RaRe-Technologies/gensim/contributors","subscribers_url":"https://api.github.com/repos/RaRe-Technologies/gensim/subscribers","subscription_url":"https://api.github.com/repos/RaRe-Technologies/gensim/subscription","commits_url":"https://api.github.com/repos/RaRe-Technologies/gensim/commits{/sha}","git_commits_url":"https://api.github.com/repos/RaRe-Technologies/gensim/git/commits{/sha}","comments_url":"https://api.github.com/repos/RaRe-Technologies/gensim/comments{/number}","issue_comment_url":"https://api.github.com/repos/RaRe-Technologies/gensim/issues/comments{/number}","contents_url":"https://api.github.com/repos/RaRe-Technologies/gensim/contents/{+path}","compare_url":"https://api.github.com/repos/RaRe-Technologies/gensim/compare/{base}...{head}","merges_url":"https://api.github.com/repos/RaRe-Technologies/gensim/merges","archive_url":"https://api.github.com/repos/RaRe-Technologies/gensim/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/RaRe-Technologies/gensim/downloads","issues_url":"https://api.github.com/repos/RaRe-Technologies/gensim/issues{/number}","pulls_url":"https://api.github.com/repos/RaRe-Technologies/gensim/pulls{/number}","milestones_url":"https://api.github.com/repos/RaRe-Technologies/gensim/milestones{/number}","notifications_url":"https://api.github.com/repos/RaRe-Technologies/gensim/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/RaRe-Technologies/gensim/labels{/name}","releases_url":"https://api.github.com/repos/RaRe-Technologies/gensim/releases{/id}","deployments_url":"https://api.github.com/repos/RaRe-Technologies/gensim/deployments","created_at":"2011-02-10T07:43:04Z","updated_at":"2017-01-01T18:53:04Z","pushed_at":"2017-01-01T15:28:17Z","git_url":"git://github.com/RaRe-Technologies/gensim.git","ssh_url":"git@github.com:RaRe-Technologies/gensim.git","clone_url":"https://github.com/RaRe-Technologies/gensim.git","svn_url":"https://github.com/RaRe-Technologies/gensim","homepage":"http://radimrehurek.com/gensim/","size":21472,"stargazers_count":3464,"watchers_count":3464,"language":"Python","has_issues":true,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":1307,"mirror_url":null,"open_issues_count":152,"forks":1307,"open_issues":152,"watchers":3464,"default_branch":"develop"}},"_links":{"self":{"href":"https://api.github.com/repos/RaRe-Technologies/gensim/pulls/893"},"html":{"href":"https://github.com/RaRe-Technologies/gensim/pull/893"},"issue":{"href":"https://api.github.com/repos/RaRe-Technologies/gensim/issues/893"},"comments":{"href":"https://api.github.com/repos/RaRe-Technologies/gensim/issues/893/comments"},"review_comments":{"href":"https://api.github.com/repos/RaRe-Technologies/gensim/pulls/893/comments"},"review_comment":{"href":"https://api.github.com/repos/RaRe-Technologies/gensim/pulls/comments{/number}"},"commits":{"href":"https://api.github.com/repos/RaRe-Technologies/gensim/pulls/893/commits"},"statuses":{"href":"https://api.github.com/repos/RaRe-Technologies/gensim/statuses/ac9ecd4ec7ba4bfbe78ae92a7d5d3d5a746d8837"}}}}
{"actor":{"display_login":"tmylk"}}