{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "# The NeurIPS Experiment\n", "\n", "### [Neil D. Lawrence](http://inverseprobability.com), University of\n", "\n", "Cambridge\n", "\n", "### 2022-05-10" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "**Abstract**: In 2014, along with Corinna Cortes, I was Program Chair of\n", "the Neural Information Processing Systems conference. At the time, when\n", "wondering about innovations for the conference, Corinna and I decided it\n", "would be interesting to test the consistency of reviewing. With this in\n", "mind, we randomly selected 10% of submissions and had them reviewed by\n", "two independent committees. In this talk I will briefly review the\n", "construction of the experiment, explain how the NeurIPS review process\n", "worked and talk about what I felt the implications for reviewing were,\n", "vs what the community reaction was." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "$$\n", "$$" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "# Introduction\n", "\n", "The NIPS experiment was an experiment to determine the consistency of\n", "the review process. After receiving papers, we selected 10% that would\n", "be independently rereviewed. The idea was to determine how consistent\n", "the decisions between the two sets of independent papers would be. In\n", "2014 NIPS received 1678 submissions and we selected 170 for the\n", "experiment. These papers are referred to below as ‘duplicated papers.’\n", "\n", "To run the experiment, we created two separate committees within the\n", "NIPS program committee. The idea was that the two separate committees\n", "would review each duplicated paper independently and results compared.\n", "\n", "## NeurIPS in Numbers\n", "\n", "\\[edit\\]\n", "\n", "In 2014 the NeurIPS conference had 1474 active reviewers (up from 1133\n", "in 2013), 92 area chairs (up from 67 in 2013) and two program chairs,\n", "Corinna Cortes and me.\n", "\n", "The conference received 1678 submissions and presented 414 accepted\n", "papers, of which 20 were presented as talks in the single-track session,\n", "62 were presented as spotlights and 331 papers were presented as\n", "posters. Of the 1678 submissions, 19 papers were rejected without\n", "review.\n", "\n", "## The NeurIPS Experiment\n", "\n", "The objective of the NeurIPS experiment was to determine how consistent\n", "the process of peer review is. One way of phrasing this question is to\n", "ask: what would happen to submitted papers in the conference if the\n", "process was independently rerun?\n", "\n", "For the 2014 conference, to explore this question, we selected\n", "$\\approx 10\\%$ of submitted papers to be reviewed twice, by independent\n", "committees. This led to 170 papers being selected from the conference\n", "for dual reviewing. For these papers the program committee was divided\n", "into two. Reviewers were placed randomly on one side of the committee or\n", "the other. For Program Chairs we also engaged in some manual selection\n", "to ensure we had expert coverage in all the conference areas on both\n", "side of the committee.\n", "\n", "## Timeline for NeurIPS\n", "\n", "\\[edit\\]\n", "\n", "Chairing a conference starts with recruitment of the program committee,\n", "which is usually done in a few stages. The primary task is to recruit\n", "the area chairs. We sent out our program committee invites in three\n", "waves.\n", "\n", "- 17/02/2014\n", "- 08/03/2014\n", "- 09/04/2014\n", "\n", "By recruiting area chairs first, you can involve them in recruiting\n", "reviewers. We requested names of reviewers from ACs in two waves.\n", "\n", "- 25/03/2014\n", "- 11/04/2014\n", "\n", "In 2014, this wasn’t enough to obtain the requisite number of reviewers,\n", "so we used additional approaches. These included lists of previous\n", "NeurIPS authors. For each individual we were looking for at least two\n", "previously-published papers from NeurIPS and other leading leading ML\n", "venues like ICML, AISTATS, COLT, UAI etc.. We made extensive use of\n", "[DBLP](https://dblp.uni-trier.de/) for verifying each potential\n", "reviewer’s publication track record.\n", "\n", "- 14/04/2014\n", "- 28/04/2014\n", "- 09/05/2014\n", "- 10/06/2014 (note this is after deadline … lots of area chairs asked\n", " for reviewers after the deadline!). We invited them en-masse.\n", "\n", "- 06/06/2014 Submission Deadline\n", "- 12/06/2014 Bidding Open for Area Chairs (this was *delayed* by CMT\n", " issues)\n", "- 17/06/2014 Bidding Open for Reviewers\n", "- 01/07/2014 Start Reviewing\n", "- 21/07/2014 Reviewing deadline\n", "- 04/08/2014 Reviews to Authors\n", "- 11/08/2014 Author Rebuttal Due\n", "- 25/08/2014 Teleconferences Begin\n", "- 30/08/2014 Teleconferences End\n", "- 1/09/2014 Preliminary Decisions Made\n", "- 9/09/2014 Decisions Sent to Authors\n", "\n", "## Paper Scoring and Reviewer Instructions\n", "\n", "\\[edit\\]\n", "\n", "The instructions to reviewers for the 2014 conference are still\n", "available [online\n", "here](https://nips.cc/Conferences/2014/PaperInformation/ReviewerInstructions).\n", "\n", "To keep quality of reviews high, we tried to keep load low. We didn’t\n", "assign any reviewer more than 5 papers, most reviewers received 4\n", "papers.\n", "\n", "## Quantitative Evaluation\n", "\n", "Reviewers give a score of between 1 and 10 for each paper. The program\n", "committee will interpret the numerical score in the following way:\n", "\n", "- 10: Top 5% of accepted NIPS papers, a seminal paper for the ages.\n", "\n", " I will consider not reviewing for NIPS again if this is rejected.\n", "\n", "- 9: Top 15% of accepted NIPS papers, an excellent paper, a strong\n", " accept.\n", "\n", " I will fight for acceptance.\n", "\n", "- 8: Top 50% of accepted NIPS papers, a very good paper, a clear\n", " accept.\n", "\n", " I vote and argue for acceptance.\n", "\n", "- 7: Good paper, accept.\n", "\n", " I vote for acceptance, although would not be upset if it were\n", " rejected.\n", "\n", "- 6: Marginally above the acceptance threshold.\n", "\n", " I tend to vote for accepting it, but leaving it out of the program\n", " would be no great loss.\n", "\n", "- 5: Marginally below the acceptance threshold.\n", "\n", " I tend to vote for rejecting it, but having it in the program would\n", " not be that bad.\n", "\n", "- 4: An OK paper, but not good enough. A rejection.\n", "\n", " I vote for rejecting it, although would not be upset if it were\n", " accepted.\n", "\n", "- 3: A clear rejection.\n", "\n", " I vote and argue for rejection.\n", "\n", "- 2: A strong rejection. I’m surprised it was submitted to this\n", " conference.\n", "\n", " I will fight for rejection.\n", "\n", "- 1: Trivial or wrong or known. I’m surprised anybody wrote such a\n", " paper.\n", "\n", " I will consider not reviewing for NIPS again if this is accepted.\n", "\n", "Reviewers should NOT assume that they have received an unbiased sample\n", "of papers, nor should they adjust their scores to achieve an artificial\n", "balance of high and low scores. Scores should reflect absolute judgments\n", "of the contributions made by each paper.\n", "\n", "## Impact Score\n", "\n", "The impact score was an innovation introduce in 2013 by Ghahramani and\n", "Welling that we retained for 2014. Quoting from the instructions to\n", "reviewers:\n", "\n", "> Independently of the Quality Score above, this is your opportunity to\n", "> identify papers that are very different, original, or otherwise\n", "> potentially impactful for the NIPS community.\n", ">\n", "> There are two choices:\n", ">\n", "> 2: This work is different enough from typical submissions to\n", "> potentially have a major impact on a subset of the NIPS community.\n", ">\n", "> 1: This work is incremental and unlikely to have much impact even\n", "> though it may be technically correct and well executed.\n", ">\n", "> Examples of situations where the impact and quality scores may point\n", "> in opposite directions include papers which are technically strong but\n", "> unlikely to generate much follow-up research, or papers that have some\n", "> flaw (e.g. not enough evaluation, not citing the right literature) but\n", "> could lead to new directions of research.\n", "\n", "## Confidence Score\n", "\n", "Reviewers also give a confidence score between 1 and 5 for each paper.\n", "The program committee will interpret the numerical score in the\n", "following way:\n", "\n", "5: The reviewer is absolutely certain that the evaluation is correct and\n", "very familiar with the relevant literature.\n", "\n", "4: The reviewer is confident but not absolutely certain that the\n", "evaluation is correct. It is unlikely but conceivable that the reviewer\n", "did not understand certain parts of the paper, or that the reviewer was\n", "unfamiliar with a piece of relevant literature.\n", "\n", "3: The reviewer is fairly confident that the evaluation is correct. It\n", "is possible that the reviewer did not understand certain parts of the\n", "paper, or that the reviewer was unfamiliar with a piece of relevant\n", "literature. Mathematics and other details were not carefully checked.\n", "\n", "2: The reviewer is willing to defend the evaluation, but it is quite\n", "likely that the reviewer did not understand central parts of the paper.\n", "\n", "1: The reviewer’s evaluation is an educated guess. Either the paper is\n", "not in the reviewer’s area, or it was extremely difficult to understand.\n", "\n", "## Qualitative Evaluation\n", "\n", "All NIPS papers should be good scientific papers, regardless of their\n", "specific area. We judge whether a paper is good using four criteria; a\n", "reviewer should comment on all of these, if possible:\n", "\n", "- Quality\n", "\n", " Is the paper technically sound? Are claims well-supported by\n", " theoretical analysis or experimental results? Is this a complete\n", " piece of work, or merely a position paper? Are the authors careful\n", " (and honest) about evaluating both the strengths and weaknesses of\n", " the work?\n", "\n", "- Clarity\n", "\n", " Is the paper clearly written? Is it well-organized? (If not, feel\n", " free to make suggestions to improve the manuscript.) Does it\n", " adequately inform the reader? (A superbly written paper provides\n", " enough information for the expert reader to reproduce its results.)\n", "\n", "- Originality\n", "\n", " Are the problems or approaches new? Is this a novel combination of\n", " familiar techniques? Is it clear how this work differs from previous\n", " contributions? Is related work adequately referenced? We recommend\n", " that you check the proceedings of recent NIPS conferences to make\n", " sure that each paper is significantly different from papers in\n", " previous proceedings. Abstracts and links to many of the previous\n", " NIPS papers are available from http://books.nips.cc\n", "\n", "- Significance\n", "\n", "Are the results important? Are other people (practitioners or\n", "researchers) likely to use these ideas or build on them? Does the paper\n", "address a difficult problem in a better way than previous research? Does\n", "it advance the state of the art in a demonstrable way? Does it provide\n", "unique data, unique conclusions on existing data, or a unique\n", "theoretical or pragmatic approach?\n", "\n", "\n", "\n", "## NeurIPS Experiment Results\n", "\n", "\\[edit\\]\n", "\n", "The results of the experiment were as follows. From 170 papers 4 had to\n", "be withdrawn or were rejected without completing the review process, for\n", "the remainder, the ‘confusion matrix’ for the two committee’s decisions\n", "is in Table .\n", "\n", "Table: Table showing the results from the two committees as a confusion\n", "matrix. Four papers were rejected or withdrawn without review.\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "
\n", "\n", "\n", "Committee 1\n", "\n", "
\n", "\n", "\n", "Accept\n", "\n", "\n", "\n", "Reject\n", "\n", "
\n", "\n", "Committee 2\n", "\n", "\n", "\n", "Accept\n", "\n", "\n", "\n", "22\n", "\n", "\n", "\n", "22\n", "\n", "
\n", "\n", "Reject\n", "\n", "\n", "\n", "21\n", "\n", "\n", "\n", "101\n", "\n", "
\n", "\n", "\n", "\n", "## Summarizing the Table\n", "\n", "There are a few ways of summarizing the numbers in this table as percent\n", "or probabilities. First, the inconsistency, the proportion of decisions\n", "that were not the same across the two committees. The decisions were\n", "inconsistent for 43 out of 166 papers or 0.259 as a proportion. This\n", "number is perhaps a natural way of summarizing the figures if you are\n", "submitting your paper and wish to know an estimate of what the\n", "probability is that your paper would have different decisions according\n", "to the different committees. Secondly, the accept precision: if you are\n", "attending the conference and looking at any given paper, then you might\n", "want to know the probability that the paper would have been rejected in\n", "an independent rerunning of the conference. We can estimate this for\n", "Committee 1’s conference as 22/(22 + 22) = 0.5 (50%) and for Committee\n", "2’s conference as 21/(22+21) = 0.49 (49%). Averaging the two estimates\n", "gives us 49.5%. Finally, the reject precision: if your paper was\n", "rejected from the conference, you might like an estimate of the\n", "probability that the same paper would be rejected again if the review\n", "process had been independently rerun. That estimate is 101/(22+101) =\n", "0.82 (82%) for Committee 1 and 101/(21+101)=0.83 (83%) for Committee 2,\n", "or on average 82.5%. A final quality estimate might be the ratio of\n", "consistent accepts to consistent rejects, or the agreed accept rate,\n", "22/123 = 0.18 (18%).\n", "\n", "- *inconsistency*: 43/166 = **0.259**\n", " - proportion of decisions that were not the same\n", "- *accept precision* $0.5 \\times 22/44$ + $0.5 \\times 21/43$ =\n", " **0.495**\n", " - probability any accepted paper would be rejected in a rerunning\n", "- *reject precision* = $0.5\\times 101/(22+101)$ +\n", " $0.5\\times 101/(21 + 101)$ = **0.175**\n", " - probability any rejected paper would be rejected in a rerunning\n", "- *agreed accept rate* = 22/101 = **0.218**\n", "- ratio between agreed accepted papers and agreed rejected papers.\n", "\n", "\n", "\n", "\n", "## Reviewer Calibration\n", "\n", "\\[edit\\]\n", "\n", "Calibration of reviewers is the process where different interpretations\n", "of the reviewing scale are addressed. The tradition of calibration goes\n", "at least as far back as John Platt’s Program Chairing, and included a\n", "Bayesian model by Ge, Welling and Ghahramani at NeurIPS 2013.\n", "\n", "## Reviewer Calibration Model\n", "\n", "\\[edit\\]\n", "\n", "In this note book we deal with reviewer calibration. Our assumption is\n", "that the score from the $j$th reviwer for the $i$th paper is given by $$\n", "y_{i,j} = f_i + b_j + \\epsilon_{i, j}\n", "$$ where $f_i$ is the ‘objective quality’ of paper $i$ and $b_j$ is an\n", "offset associated with reviewer $j$. $\\epsilon_{i,j}$ is a subjective\n", "quality estimate which reflects how a specific reviewer’s opinion\n", "differs from other reviewers (such differences in opinion may be due to\n", "differing expertise or perspective). The underlying ‘objective quality’\n", "of the paper is assumed to be the same for all reviewers and the\n", "reviewer offset is assumed to be the same for all papers.\n", "\n", "If we have $n$ papers and $m$ reviewers, then this implies $n$ + $m$ +\n", "$nm$ values need to be estimated. Naturally this is too many, and we can\n", "start by assuming that the subjective quality is drawn from a normal\n", "density with variance $\\sigma^2$ $$\n", "\\epsilon_{i, j} \\sim N(0, \\sigma^2 \\mathbf{I})\n", "$$ which reduces us to $n$ + $m$ + 1 parameters. Further we can assume\n", "that the objective quality is also normally distributed with mean $\\mu$\n", "and variance $\\alpha_f$, $$\n", "f_i \\sim N(\\mu, \\alpha_f)\n", "$$ this now reduces us to $m$+3 parameters. However, we only have\n", "approximately $4m$ observations (4 papers per reviewer) so parameters\n", "may still not be that well determined (particularly for those reviewers\n", "that have only one review). We, therefore, finally, assume that reviewer\n", "offset is normally distributed with zero mean, $$\n", "b_j \\sim N(0, \\alpha_b),\n", "$$ leaving us only four parameters: $\\mu$, $\\sigma^2$, $\\alpha_f$ and\n", "$\\alpha_b$. Combined together these three assumptions imply that $$\n", "\\mathbf{y} \\sim N(\\mu \\mathbf{1}, \\mathbf{K}),\n", "$$ where $\\mathbf{y}$ is a vector of stacked scores $\\mathbf{1}$ is the\n", "vector of ones and the elements of the covariance function are given by\n", "$$\n", "k(i,j; k,l) = \\delta_{i,k} \\alpha_f + \\delta_{j,l} \\alpha_b + \\delta_{i, k}\\delta_{j,l} \\sigma^2,\n", "$$ where $i$ and $j$ are the index of first paper and reviewer and $k$\n", "and $l$ are the index of second paper and reviewer. The mean is easily\n", "estimated by maximum likelihood and is given as the mean of all scores.\n", "\n", "It is convenient to reparametrize slightly into an overall scale\n", "$\\alpha_f$, and normalized variance parameters, $$\n", "k(i,j; k,l) = \\alpha_f\\left(\\delta_{i,k} + \\delta_{j,l} \\frac{\\alpha_b}{\\alpha_f} + \\delta_{i, k}\\delta_{j,l} \\frac{\\sigma^2}{\\alpha_f}\\right)\n", "$$ which we rewrite to give two ratios: offset/signal ratio,\n", "$\\hat{\\alpha}_b$ and noise/signal $\\hat{\\sigma}^2$ ratio. $$\n", "k(i,j; k,l) = \\alpha_f\\left(\\delta_{i,k} + \\delta_{j,l} \\hat{\\alpha}_b + \\delta_{i, k}\\delta_{j,l} \\hat{\\sigma}^2\\right)\n", "$$ The advantage of this parameterization is it allows us to optimize\n", "$\\alpha_f$ directly (with a fixed-point equation) and it will be very\n", "well determined. This leaves us with two free parameters, that we can\n", "explore on the grid. It is in these parameters that we expect the\n", "remaining underdetermindness of the model. We expect $\\alpha_f$ to be\n", "well determined because the negative log likelihood is now $$\n", "\\frac{|\\mathbf{y}|}{2}\\log\\alpha_f + \\frac{1}{2}\\log \\left|\\hat{\\mathbf{K}}\\right| + \\frac{1}{2\\alpha_f}\\mathbf{y}^\\top \\hat{\\mathbf{K}}^{-1} \\mathbf{y},\n", "$$ where $|\\mathbf{y}|$ is the length of $\\mathbf{y}$ (i.e. the number\n", "of reviews) and $\\hat{\\mathbf{K}}=\\alpha_f^{-1}\\mathbf{K}$ is the scale\n", "normalized covariance. This negative log likelihood is easily minimized\n", "to recover $$\n", "\\alpha_f = \\frac{1}{|\\mathbf{y}|} \\mathbf{y}^\\top \\hat{\\mathbf{K}}^{-1} \\mathbf{y}.\n", "$$ A Bayesian analysis of this parameter is possible with gamma priors,\n", "but it would merely show that this parameter is extremely well\n", "determined (the degrees of freedom parameter of the associated\n", "Student-$t$ marginal likelihood scales will the number of reviews, which\n", "will be around $|\\mathbf{y}| \\approx 6,000$ in our case.\n", "\n", "So, we propose to proceed as follows. Set the mean from the reviews\n", "($\\mu$) and then choose a two-dimensional grid of parameters for\n", "reviewer offset and diversity. For each parameter choice, optimize to\n", "find $\\alpha_f$ and then evaluate the liklihood. Worst case this will\n", "require us inverting $\\hat{\\mathbf{K}}$, but if the reviewer paper\n", "groups are disconnected, it can be done a lot quicker. Next stage is to\n", "load in the reviews for analysis.\n", "\n", "## Fitting the Model\n", "\n", "\\[edit\\]\n", "\n", "``` python\n", "import cmtutils as cu\n", "import os\n", "import pandas as pd\n", "import numpy as np\n", "import GPy\n", "from scipy.sparse.csgraph import connected_components\n", "from scipy.linalg import solve_triangular \n", "```\n", "\n", "``` python\n", "date = '2014-09-06'\n", "```\n", "\n", "## Loading in the Data\n", "\n", "``` python\n", "filename = date + '_reviews.xls'\n", "reviews = cu.CMT_Reviews_read(filename=filename)\n", "papers = list(sorted(set(reviews.reviews.index), key=int))\n", "reviews.reviews = reviews.reviews.loc[papers]\n", "```\n", "\n", "The maximum likelihood solution for $\\mu$ is simply the mean quality of\n", "the papers, this is easily computed.\n", "\n", "``` python\n", "mu = reviews.reviews.Quality.mean()\n", "print(\"Mean value, mu = \", mu)\n", "```\n", "\n", "## Data Preparation\n", "\n", "We take the reviews, which are indexed by the paper number, and create a\n", "new data frame, that indexes by paper id and email combined. From these\n", "reviews we tokenize the `PaperID` and the `Email` to extract two\n", "matrices that can be used in creation of covariance matrices. We also\n", "create a target vector which is the mean centred vector of scores.\n", "\n", "``` python\n", "r = reviews.reviews.reset_index()\n", "r.rename(columns={'ID':'PaperID'}, inplace=True)\n", "r.index = r.PaperID + '_' + r.Email\n", "X1 = pd.get_dummies(r.PaperID)\n", "X1 = X1[sorted(X1.columns, key=int)]\n", "X2 = pd.get_dummies(r.Email)\n", "X2 = X2[sorted(X2.columns, key=str.lower)]\n", "y = reviews.reviews.Quality - mu\n", "```\n", "\n", "### Constructing the Model in GPy\n", "\n", "Having reduced the model to two parameters, I was hopeful I could set\n", "parameters broadly by hand. My initial expectation was that `alpha_b`\n", "and `sigma2` would both be less than 1, but some playing with parameters\n", "showed this wasn’t the case. Rather than waste further time, I decided\n", "to use our [`GPy` Software](https://github.com/SheffieldML/GPy) (see\n", "below) to find a maximum likelihood solution for the parameters.\n", "\n", "Model construction firstly involves constructing covariance functions\n", "for the model and concatenating `X1` and `X2` to a new input matrix `X`.\n", "\n", "``` python\n", "X = X1.join(X2)\n", "kern1 = GPy.kern.Linear(input_dim=len(X1.columns), active_dims=np.arange(len(X1.columns)))\n", "kern1.name = 'K_f'\n", "kern2 = GPy.kern.Linear(input_dim=len(X2.columns), active_dims=np.arange(len(X1.columns), len(X.columns)))\n", "kern2.name = 'K_b'\n", "```\n", "\n", "Next, the covariance function is used to create a Gaussian process\n", "regression model with `X` as input and `y` as target. The covariance\n", "function is given by $\\mathbf{K}_f + \\mathbf{K}_b$.\n", "\n", "``` python\n", "model = GPy.models.GPRegression(X, y.to_numpy()[:, np.newaxis], kern1+kern2)\n", "model.optimize()\n", "```\n", "\n", "Now we can check the parameters of the result.\n", "\n", "``` python\n", "print(model)\n", "print(model.log_likelihood())\n", "```\n", "\n", " Name : GP regression\n", " Objective : 10071.679092815619\n", " Number of Parameters : 3\n", " Number of Optimization Parameters : 3\n", " Updates : True\n", " Parameters:\n", " GP_regression. | value | constraints | priors\n", " sum.K_f.variances | 1.2782303448777643 | +ve | \n", " sum.K_b.variances | 0.2400098787580176 | +ve | \n", " Gaussian_noise.variance | 1.2683656892796749 | +ve | \n", " -10071.679092815619\n", "\n", "### Construct the Model Without GPy\n", "\n", "The answer from the GPy solution is introduced here, alongside the code\n", "where the covariance matrices are explicitly created (above they are\n", "created using GPy’s high level code for kernel matrices, which may be\n", "less clear on the details).\n", "\n", "``` python\n", "# set parameter values to ML solutions given by GPy.\n", "alpha_f = model.sum.K_f.variances\n", "alpha_b = model.sum.K_b.variances/alpha_f\n", "sigma2 = model.Gaussian_noise.variance/alpha_f\n", "```\n", "\n", "Now we create the covariance functions based on the tokenized paper IDs\n", "and emails.\n", "\n", "``` python\n", "K_f = np.dot(X1, X1.T)\n", "K_b = alpha_b*np.dot(X2, X2.T)\n", "K = K_f + K_b + sigma2*np.eye(X2.shape[0])\n", "Kinv, L, Li, logdet = GPy.util.linalg.pdinv(K) # since we have GPy loaded in use their positive definite inverse.\n", "y = reviews.reviews.Quality - mu\n", "alpha = np.dot(Kinv, y)\n", "yTKinvy = np.dot(y, alpha)\n", "alpha_f = yTKinvy/len(y)\n", "```\n", "\n", "Since we have removed the data mean, the log likelihood we are\n", "interested in is the likelihood of a multivariate Gaussian with\n", "covariance $\\mathbf{K}$ and mean zero. This is computed below.\n", "\n", "``` python\n", "ll = 0.5*len(y)*np.log(2*np.pi*alpha_f) + 0.5*logdet + 0.5*yTKinvy/alpha_f \n", "print(\"negative log likelihood: \", ll)\n", "```\n", "\n", "### Review Quality Prediction\n", "\n", "\\[edit\\]\n", "\n", "Now we wish to predict the bias corrected scores for the papers. That\n", "involves considering a variable $s_{i,j} = f_i + e_{i,j}$ which is the\n", "score with the bias removed. That variable has a covariance matrix,\n", "$\\mathbf{K}_s=\\mathbf{K}_f + \\sigma^2 \\mathbf{I}$ and a cross covariance\n", "between $\\mathbf{y}$ and $\\mathbf{s}$ is also given by $\\mathbf{K}_s$.\n", "This means we can compute the posterior distribution of the scores as\n", "follows:\n", "\n", "``` python\n", "# Compute mean and covariance of quality scores\n", "K_s = K_f + np.eye(K_f.shape[0])*sigma2\n", "s = pd.Series(np.dot(K_s, alpha) + mu, index=X1.index)\n", "covs = alpha_f*(K_s - np.dot(K_s, np.dot(Kinv, K_s)))\n", "```\n", "\n", "### Monte Carlo Simulations for Probability of Acceptance\n", "\n", "\\[edit\\]\n", "\n", "We can now sample from this posterior distribution of bias-adjusted\n", "scores jointly, to get a set of scores for all papers. For this set of\n", "scores, we can perform a ranking and accept the top 400 papers. This\n", "gives us a sampled conference. If we do that 1,000 times then we can see\n", "how many times each paper was accepted to get a probability of\n", "acceptance.\n", "\n", "``` python\n", "number_accepts = 420 # 440 because of the 10% replication\n", "```\n", "\n", "``` python\n", "# place this in a separate box, because sampling can take a while.\n", "samples = 1000\n", "score = np.random.multivariate_normal(mean=s, cov=covs, size=samples).T\n", "# Use X1 which maps papers to paper/reviewer pairings to get the average score for each paper.\n", "paper_score = pd.DataFrame(np.dot(np.diag(1./X1.sum(0)), np.dot(X1.T, score)), index=X1.columns)\n", "```\n", "\n", "Now we can compute the probability of acceptance for each of the sampled\n", "rankings.\n", "\n", "``` python\n", "prob_accept = ((paper_score>paper_score.quantile(1-(float(number_accepts)/paper_score.shape[0]))).sum(1)/1000)\n", "prob_accept.name = 'AcceptProbability'\n", "```\n", "\n", "Now we have the probability of accepts, we can decide on the boundaries\n", "of the grey area. These are set in `lower` and `upper`. The grey area is\n", "those papers that will be debated most heavily during the\n", "teleconferences between program chairs and area chairs.\n", "\n", "``` python\n", "lower=0.1\n", "upper=0.9\n", "grey_area = ((prob_accept>lower) & (prob_accept\n", "\n", "Figure: Histogram of the probability of accept as estimated by the\n", "Monte Carlo simulation across all papers submitted to NeurIPS 2014.\n", "\n", "## Some Sanity Checking Plots\n", "\n", "\\[edit\\]\n", "\n", "Here is the histogram of the reviewer scores after calibration.\n", "\n", "``` python\n", "fig, ax = plt.subplots(figsize=plot.big_wide_figsize)\n", "s.hist(bins=100, ax=ax)\n", "_ = ax.set_title('Calibrated Reviewer Scores')\n", "ma.write_figure(directory=\"./neurips\", filename=\"calibrated-reviewer-scores.svg\")\n", "```\n", "\n", "\n", "\n", "Figure: Histogram of updated reviewer scores after the calibration\n", "process is applied.\n", "\n", "### Adjustments to Reviewer Scores\n", "\n", "We can also compute the posterior distribution for the adjustments to\n", "the reviewer scores.\n", "\n", "``` python\n", "# Compute mean and covariance of review biases\n", "b = pd.Series(np.dot(K_b, alpha), index=X2.index)\n", "covb = alpha_f*(K_b - np.dot(K_b, np.dot(Kinv, K_b)))\n", "```\n", "\n", "``` python\n", "reviewer_bias = pd.Series(np.dot(np.diag(1./X2.sum(0)), np.dot(X2.T, b)), index=X2.columns, name='ReviewerBiasMean')\n", "reviewer_bias_std = pd.Series(np.dot(np.diag(1./X2.sum(0)), np.dot(X2.T, np.sqrt(np.diag(covb)))), index=X2.columns, name='ReviewerBiasStd')\n", "```\n", "\n", "Here is a histogram of the mean adjustment for the reviewers.\n", "\n", "``` python\n", "fig, ax = plt.subplots(figsize=plot.big_wide_figsize)\n", "reviewer_bias.hist(bins=100, ax=ax)\n", "_ = ax.set_title('Reviewer Calibration Adjustments Histogram')\n", "ma.write_figure(directory=\"./neurips\", filename=\"reviewer-calibration-adjustments.svg\")\n", "```\n", "\n", "\n", "\n", "Figure: Histogram of individual offsets associated with the reviewers\n", "as estimated by the model.\n", "\n", "Export a version of the bias scores for use in CMT.\n", "\n", "``` python\n", "bias_export = pd.DataFrame(data={'Quality Score - Does the paper deserves to be published?':reviewer_bias, \n", " 'Impact Score - Independently of the Quality Score above, this is your opportunity to identify papers that are very different, original, or otherwise potentially impactful for the NIPS community.':pd.Series(np.zeros(len(reviewer_bias)), index=reviewer_bias.index),\n", " 'Confidence':pd.Series(np.zeros(len(reviewer_bias)), index=reviewer_bias.index)})\n", "cols = bias_export.columns.tolist()\n", "cols = [cols[2], cols[1], cols[0]]\n", "bias_export = bias_export[cols]\n", "#bias_export.to_csv(os.path.join(cu.cmt_data_directory, 'reviewer_bias.csv'), sep='\\t', header=True, index_label='Reviewer Email')\n", "```\n", "\n", "## Sanity Check\n", "\n", "As a sanity check Corinna suggested it makes sense to plot the average\n", "raw score for the papers vs the probability of accept, just to ensure\n", "nothing weird is going on. To clarify the plot, I’ve actually plotted\n", "raw score vs log odds of accept.\n", "\n", "``` python\n", "raw_score = pd.Series(np.dot(np.diag(1./X1.sum(0)), np.dot(X1.T, r.Quality)), index=X1.columns)\n", "prob_accept[prob_accept==0] = 1/(10*samples)\n", "prob_accept[prob_accept==1] = 1-1/(10*samples)\n", "```\n", "\n", "``` python\n", "fig, ax = plt.subplots(figsize=plot.big_wide_figsize)\n", "ax.plot(raw_score, np.log(prob_accept)- np.log(1-prob_accept), 'rx')\n", "ax.set_title('Raw Score vs Log odds of accept')\n", "ax.set_xlabel('raw score')\n", "_ = ax.set_ylabel('log odds of accept')\n", "ma.write_figure(directory=\"./neurips\", filename=\"raw-score-vs-log-odds.svg\")\n", "```\n", "\n", "\n", "\n", "Figure: Scatter plot of the raw paper score against the log\n", "probability of paper acceptance, as estimated by Monte Carlo\n", "simulation.\n", "\n", "## Calibraton Quality Sanity Checks\n", "\n", "``` python\n", "s.name = 'CalibratedQuality'\n", "r = r.join(s)\n", "```\n", "\n", "We can also look at a scatter plot of the review quality vs the\n", "calibrated quality.\n", "\n", "``` python\n", "import matplotlib.plt as plt\n", "import cmtutils.plot as plot\n", "```\n", "\n", "``` python\n", "fig, ax = plt.subplots(figsize=plot.big_wide_figsize)\n", "ax.plot(r.Quality, r.CalibratedQuality, 'r.', markersize=10)\n", "ax.set_xlim([0, 11])\n", "ax.set_xlabel('original review score')\n", "_ = ax.set_ylabel('calibrated review score')\n", "ma.write_figure(directory=\"./neurips\", filename=\"calibrated-review-score-vs-original-score.svg\")\n", "```\n", "\n", "\n", "\n", "Figure: Scatter plot of the calibrated review scores against the\n", "original review scores.\n", "\n", "## Correlation of Duplicate Papers\n", "\n", "\\[edit\\]\n", "\n", "For NeurIPS 2014 we experimented with duplicate papers: we pushed papers\n", "through the system twice, exposing them to different subsets of the\n", "reviewers. The first thing we’ll look at is the duplicate papers.\n", "Firstly, we identify them by matching on title.\n", "\n", "``` python\n", "filename = date + '_paper_list.xls'\n", "papers = cu.CMT_Papers_read(filename=filename)\n", "duplicate_list = []\n", "for ID, title in papers.papers.Title.iteritems():\n", " if int(ID)>1779 and int(ID) != 1949:\n", " pair = list(papers.papers[papers.papers['Title'].str.contains(papers.papers.Title[ID].strip())].index)\n", " pair.sort(key=int)\n", " duplicate_list.append(pair)\n", "```\n", "\n", "Next, we compute the correlation coefficients for the duplicated papers\n", "for the average impact and quality scores.\n", "\n", "``` python\n", "quality = []\n", "calibrated_quality = []\n", "accept = []\n", "impact = []\n", "confidence = []\n", "for duplicate_pair in duplicate_list:\n", " quality.append([np.mean(r[r.PaperID==duplicate_pair[0]].Quality), np.mean(r[r.PaperID==duplicate_pair[1]].Quality)])\n", " calibrated_quality.append([np.mean(r[r.PaperID==duplicate_pair[0]].CalibratedQuality), np.mean(r[r.PaperID==duplicate_pair[1]].CalibratedQuality)])\n", " impact.append([np.mean(r[r.PaperID==duplicate_pair[0]].Impact), np.mean(r[r.PaperID==duplicate_pair[1]].Impact)])\n", " confidence.append([np.mean(r[r.PaperID==duplicate_pair[0]].Conf), np.mean(r[r.PaperID==duplicate_pair[1]].Conf)])\n", "quality = np.array(quality)\n", "calibrated_quality = np.array(calibrated_quality)\n", "impact = np.array(impact)\n", "confidence = np.array(confidence)\n", "quality_cor = np.corrcoef(quality.T)[0, 1]\n", "calibrated_quality_cor = np.corrcoef(calibrated_quality.T)[0, 1]\n", "impact_cor = np.corrcoef(impact.T)[0, 1]\n", "confidence_cor = np.corrcoef(confidence.T)[0, 1]\n", "print(\"Quality correlation: \", quality_cor)\n", "print(\"Calibrated Quality correlation: \", calibrated_quality_cor)\n", "print(\"Impact correlation: \", impact_cor)\n", "print(\"Confidence correlation: \", confidence_cor)\n", "```\n", "\n", " Quality correlation: 0.54403674862622\n", " Calibrated Quality correlation: 0.5455958618174274\n", " Impact correlation: 0.26945269236041036\n", " Confidence correlation: 0.3854251559444674\n", "\n", "## Correlation Plots\n", "\n", "To visualize the quality score correlation, we plot the group 1 papers\n", "against the group 2 papers. Here we add a small amount of jitter to\n", "ensure points to help visualize points that would otherwise fall on the\n", "same position.\n", "\n", "``` python\n", "fig, ax = plt.subplots(figsize=plot.big_figsize)\n", "ax.plot(quality[:, 0]+np.random.randn(quality.shape[0])*0.06125, quality[:, 1]+np.random.randn(quality.shape[0])*0.06125, 'r.', markersize=10)\n", "lims = [1.5, 8.5]\n", "ax.set_xlim(lims)\n", "ax.set_ylim(lims)\n", "ax.plot(lims, lims, 'r-')\n", "_ = ax.set_title(Correlation: {cor:.2g}'.format(cor=quality_cor))\n", "ma.write_figure(directory=\"./neurips\",\n", " filename=\"quality-correlation.svg\")\n", "```\n", "\n", "\n", "\n", "Figure: Correlation between reviewer scores across the duplicated\n", "committees (scores have jitter added to prevent too many points sitting\n", "on top of each other).\n", "\n", "Similarly for the calibrated quality of the papers.\n", "\n", "``` python\n", "fig, ax = plt.subplots(figsize=plot.big_figsize)\n", "ax.plot(calibrated_quality[:, 0]+np.random.randn(calibrated_quality.shape[0])*0.06125, calibrated_quality[:, 1]+np.random.randn(calibrated_quality.shape[0])*0.06125, 'r.', markersize=10)\n", "lims = [1.5, 8.5]\n", "ax.set_xlim(lims)\n", "ax.set_ylim(lims)\n", "ax.plot(lims, lims, 'r-')\n", "_ = ax.set_title('Correlation: {cor:.2g}'.format(cor=calibrated_quality_cor))\n", "ma.write_figure(directory=\"./neurips\",\n", " filename=\"calibrated-quality-correlation.svg\")\n", "```\n", "\n", "\n", "\n", "Figure: Correlation between calibrated reviewer scores across the two\n", "independent committees.\n", "\n", "``` python\n", "# Apply Laplace smoothing to accept probabilities before incorporating them.\n", "revs = r.join((prob_accept+0.0002)/1.001, on='PaperID').join(reviewer_bias, on='Email').join(papers.papers['Number Of Discussions'], on='PaperID').join(reviewer_bias_std, on='Email').sort_values(by=['AcceptProbability','PaperID', 'CalibratedQuality'], ascending=False)\n", "revs.set_index(['PaperID'], inplace=True)\n", "def len_comments(x):\n", " return len(x.Comments)\n", "revs['comment_length']=revs.apply(len_comments, axis=1)\n", "# Save the computed information to disk\n", "#revs.to_csv(os.path.join(cu.cmt_data_directory, date + '_processed_reviews.csv'), encoding='utf-8')\n", "```\n", "\n", "## Conference Simulation\n", "\n", "\\[edit\\]\n", "\n", "Given the realization that roughly 50% of the score seems to be\n", "‘subjective’ and 50% of the score seems to be ‘objective,’ then we can\n", "simulate the conference and see what it does for the accept precision\n", "for different probability of accept.\n", "\n", "To explore the effect of the subjective scoring on the accept precision\n", "we construct a simple simulation that scores hypothetical papers with\n", "random values drawn from a Gaussian density. Each paper has an\n", "underlying objective score (shared across the hypothetical reviewers),\n", "and then alongside it there are Gaussian variables drawn independently\n", "at random to represent the subjectivity of the hypothetical reviewers.\n", "\n", "Each paper is rated by two independent committees, and the papers are\n", "reordered to accept the top $x$% where $x$ is our chosen accept rate. We\n", "can then use sample based estimates for the resulting accept precision.\n", "\n", "In these experiments the scores are taken to be 50% subjective and 50%\n", "objective, in line with the results we see from the NeurIPS 2014\n", "calibration model. We vary the number of reviewers in the simulation to\n", "see the effect of increasing reviewers on the accept precision.\n", "\n", "``` python\n", "import numpy as np\n", "```\n", "\n", "We repeat the experiment `samples` number of times, here we’ve set this\n", "to be 100000. The subjectivity portion gives how much of the scores for\n", "each paper is subjective.\n", "\n", "``` python\n", "num_papers = 100000\n", "subjectivity_portion = 0.5\n", "```\n", "\n", "``` python\n", "accept_rates = [0.05, 0.1, 0.15, 0.2, 0.25, \n", " 0.3, 0.35, 0.4, 0.45, 0.5, \n", " 0.55, 0.6, 0.65, 0.7, 0.75, \n", " 0.8, 0.85, 0.9, 0.95, 1.0]\n", "all_accepts = []\n", "for num_reviewers in range(1,7):\n", " consistent_accepts = []\n", " for accept_rate in accept_rates:\n", " objective = (1-subjectivity_portion)*np.random.randn(num_papers) \n", " subjective_0 = subjectivity_portion*np.random.randn(num_papers, num_reviewers).mean(1)\n", " subjective_1 = subjectivity_portion*np.random.randn(num_papers, num_reviewers).mean(1)\n", " score_0 = objective + subjective_0 \n", " score_1 = objective + subjective_1\n", "\n", " accept_0 = score_0.argsort()[:int(num_papers*accept_rate)]\n", " accept_1 = score_1.argsort()[:int(num_papers*accept_rate)]\n", "\n", " consistent_accept = len(set(accept_0).intersection(set(accept_1)))\n", " consistent_accepts.append(consistent_accept/(num_papers*accept_rate))\n", " print('Percentage consistently accepted: {prop}'.format(prop=consistent_accept/(num_papers*accept_rate)))\n", "\n", " all_accepts.append(consistent_accepts)\n", "all_accepts = np.array(all_accepts)\n", "consistent_accepts = np.array(consistent_accepts)\n", "accept_rate = np.array(accept_rate)\n", "```\n", "\n", "``` python\n", "import matplotlib.pyplot as plt\n", "import mlai\n", "import mlai.plot as plot\n", "from cycler import cycler\n", "monochrome = (cycler('color', ['k']) * cycler('linestyle', ['-', '--', ':']) * cycler('marker', ['^','o', 's']))\n", "```\n", "\n", "``` python\n", "fig, ax = plt.subplots(figsize=plot.big_figsize)\n", "ax.set_prop_cycle(monochrome)\n", "\n", "ax.plot(accept_rates, accept_rates, \"k-\", linewidth=2)\n", "ax.plot(accept_rates, all_accepts.T, markersize=7)\n", "ax.legend(['random', '1 reviewer', '2 reviewers', '3 reviewers', '4 reviewers', '5 reviewers', '6 reviewers'])\n", "ax.set_xlabel(\"accept rate\")\n", "ax.set_ylabel(\"accept precision\")\n", "ax.axvline(0.23)\n", "ax.grid(True)\n", "mlai.write_figure(filename=\"accept-precision-vs-accept-rate.svg\",\n", " directory=\"./neurips/\")\n", "```\n", "\n", "\n", "\n", "Figure: Plot of the accept rate vs the accept precision for the\n", "conference for 50% subjectivity and different numbers of reviewers. The\n", "grey line gives the NeurIPS accept rate for 2014 of 23%.\n", "\n", "In Figure we see the change in accept precision as we vary accept rate\n", "and number of reviewers for a conference where reviewers are 50%\n", "subjective.\n", "\n", "``` python\n", "fig, ax = plt.subplots(figsize=plot.big_figsize)\n", "ax.set_prop_cycle(monochrome)\n", "ax.plot(accept_rates, (all_accepts-accept_rates).T)\n", "ax.legend(['1 reviewer', '2 reviewers', '3 reviewers', '4 reviewers', '5 reviewers', '6 reviewers'])\n", "ax.set_xlabel(\"accept rate\")\n", "ax.set_ylabel(\"(accept precision)-(accept rate)\")\n", "mlai.write_figure(filename=\"gain-in-consistency.svg\",\n", " directory=\"./neurips/\")\n", "```\n", "\n", "\n", "\n", "Figure: Plot of the accept rate vs gain in consistency over a random\n", "conference for 50% subjectivity.\n", "\n", "Figure shows the accept rate against the gain in accept precision we\n", "have over the random committee.\n", "\n", "## Where do Rejected Papers Go?\n", "\n", "\\[edit\\]\n", "\n", "One facet that we can explore is what the final fate of papers that are\n", "rejected by the conference is.\n", "\n", "Of the 1,678 papers submitted to NeurIPS 2014, only 414 were presented\n", "at the final conference. Here we trace the fate of the rejected papers,\n", "we searched Semantic Scholar for evidence of all 1,264 rejected papers.\n", "We looked for papers with similar titles and where the NeurIPS\n", "submission’s contact author was also in the author list. We were able to\n", "track down 680 papers.\n", "\n", "This code analyzes those 680 papers extracting their final publication\n", "venue using the Semantic Scholar API.\n", "\n", "``` python\n", "%pip install cmtutils\n", "```\n", "\n", "``` python\n", "import cmtutils.nipsy as nipsy\n", "```\n", "\n", "``` python\n", "import os\n", "import yaml\n", "```\n", "\n", "``` python\n", "with open(os.path.join(nipsy.review_store, nipsy.outlet_name_mapping), 'r') as f:\n", " mapping = yaml.load(f, Loader=yaml.FullLoader)\n", "\n", "date = \"2021-06-11\"\n", "\n", "citations = nipsy.load_citation_counts(date=date)\n", "decisions = nipsy.load_decisions()\n", "nipsy.augment_decisions(decisions)\n", "joindf = nipsy.join_decisions_citations(decisions, citations)\n", "\n", "joindf['short_venue'] = joindf.venue.replace(mapping)\n", "```\n", "\n", "\n", "\n", "Figure: Sankey diagram showing the flow of NeurIPS papers through the\n", "system from submission to eventual publication.\n", "\n", "Of the 680 papers 177 were only found on arXiv, 76 were found as PDFs\n", "online without a publication venue and 427 were published in other\n", "venues. The outlets that received ten or more papers from this group\n", "were AAAI (72 papers), AISTATS (57 papers), ICML (33 papers), CVPR (17\n", "papers), Later NeurIPS (15 papers), JMLR (14 papers), IJCAI (14 papers),\n", "ICLR (13 papers), UAI (11 papers). Opinion about quality of these\n", "different outlets will vary from individual, but from our perspective\n", "all of these outlets are \\`top-tier’ for machine learning and related\n", "areas. Other papers appeared at less prestigious outlets, and citation\n", "scores were also recored for papers that remained available only on\n", "ArXiv. Note that there is likely a bias towards outlets that have a\n", "submission deadline shortly after NeurIPS decisions are public,\n", "e.g. submission deadline for AAAI 2015 was six days after NeurIPS\n", "decisions were sent to authors. AISTATS has a submission deadline one\n", "month after.\n", "\n", "A Sankey diagram showing where papers submitted to the conference ended\n", "up is shown below.\n", "\n", "``` python\n", "import plotly.graph_objects as go\n", "```\n", "\n", "``` python\n", "thresh_to_show = 3\n", "\n", "label = ['submitted', 'oral', 'spotlight', 'poster', 'reject', '/dev/null']\n", "x = [0.1, 0.3, 0.3, 0.3, 0.3, 0.5]\n", "y = [0.4, 0.95, 0.9, 0.85, 0.3, 0.01]\n", "source = [0, 0, 0, 0, 4]\n", "target = [1, 2, 3, 4, 5]\n", "value = [(joindf['Status']=='Oral').sum(),\n", " (joindf['Status']=='Spotlight').sum(), \n", " (joindf['Status']=='Poster').sum(),\n", " (joindf['Status']=='Reject').sum(),\n", " joindf.loc[joindf.reject]['venue'].isna().sum()]\n", "\n", "venue_counts = joindf.loc[joindf.reject]['short_venue'].value_counts()\n", "venue_show = venue_counts[venue_counts>=thresh_to_show]\n", "target_val = target[-1]\n", "for venue,count in venue_show.items():\n", " target_val += 1\n", " value.append(count)\n", " source.append(4)\n", " label.append(venue)\n", " target.append(target_val)\n", " if venue=='ArXiv':\n", " y.append(.15)\n", " x.append(0.75)\n", " \n", " elif venue == 'None':\n", " y.append(.20)\n", " x.append(0.75)\n", "\n", " else: \n", " y.append(.27)\n", " x.append(0.8)\n", " \n", "\n", " \n", "value.append(venue_counts[venue_counts\n", "\n", "Figure: Sankey diagram showing the flow of NeurIPS papers through the\n", "system from submission to eventual publication.\n", "\n", "\n", "\n", "" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Impact of Papers Seven Years On\n", "\n", "\\[edit\\]\n", "\n", "Now we look at the actual impact of the papers published using the\n", "Semantic Scholar data base for tracking citations." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "import matplotlib.pyplot as plt\n", "plt.rcParams.update({'font.size': 22})" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "import cmtutils as cu\n", "import cmtutils.nipsy as nipsy\n", "import cmtutils.plot as plot" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "import pandas as pd\n", "import numpy as np" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "papers = cu.Papers()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "UPDATE_IMPACTS = False # Set to True to download impacts from Semantic Scholar" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "The impact of the different papers is downloaded from Semantic scholar\n", "using their REST API. This can take some time, and they also throttle\n", "the calls. At the moment the code below deosn’t handle the throttling\n", "correctly. However, you it will load the cached version of of citations\n", "scores from the given date." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "if UPDATE_IMPACTS:\n", " from datetime import datetime\n", " date=datetime.today().strftime('%Y-%m-%d')\n", "else:\n", " date = \"2021-06-11\"" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# Rerun to download impacts from Semantic Scholar\n", "if UPDATE_IMPACTS:\n", " semantic_ids = nipsy.load_semantic_ids()\n", " citations_dict = citations.to_dict(orient='index')\n", " # Need to be a bit cleverer here. Semantic scholar will throttle this call.\n", " sscholar = nipsy.download_citation_counts(citations_dict=citations_dict, semantic_ids=semantic_ids)\n", " citations = pd.DataFrame.from_dict(citations_dict, orient=\"index\") \n", " citations.to_pickle(date + '-semantic-scholar-info.pickle')\n", "else: \n", " citations = nipsy.load_citation_counts(date=date)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "The final decision sheet provides information about what happened to all\n", "of the papers." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "decisions = nipsy.load_decisions()\n", "nipsy.augment_decisions(decisions)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "This is joined with the citation information to provide our main ability\n", "to understand the impact of these papers." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "joindf = nipsy.join_decisions_citations(decisions, citations)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Correlation of Quality Scores and Citation\n", "\n", "Our first study will be to check the correlation between quality scores\n", "of papers and how many times that the papers have been cited in\n", "practice. In the plot below, rejected papers are given as crosses,\n", "accepted papers are given as dots. We include all papers, whether\n", "published in a venue or just available through ArXiv or other preprint\n", "servers. We show the published/non-published quality scores and\n", "$\\log_{10}(1+\\text{citations})$ for all papers in the plot below. In the\n", "plot we are showing each point corrupted by some Laplacian noise and\n", "also removing axes. The idea is to give a sense of the distribution\n", "rather than reveal the score of a particular paper." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "import matplotlib.pyplot as plt\n", "import mlai as ma" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "column = \"average_calibrated_quality\"\n", "filter_col = \"all\"\n", "fig, ax = plt.subplots(figsize=plot.big_wide_figsize)\n", "plot.log_one_citations(column, joindf, filt=joindf[filter_col], ax=ax)\n", "ax.set_xticks([])\n", "ma.write_figure(filename=\"citations-vs-{col}-{filt}.svg\".format(filt=filter_col, col=column.replace(\"_\", \"-\")),\n", " directory=\"./neurips\")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "\n", "\n", "Figure: Scatter plot of $\\log_{10}(1+\\text{citations})$ against the\n", "average calibrated quality score for all papers. To prevent\n", "reidentification of individual papers quality scores and citation count,\n", "each point is corrupted by differentially private noise in the plot\n", "(correlation is computed before adding differentially private\n", "noise).\n", "\n", "The correlation seems strong, but of course, we are looking at papers\n", "which were accepted and rejected by the conference. This is dangerous,\n", "as it is quite likely that presentation at the conference may provide\n", "some form of lift to the papers’ numbers of citations. So, the right\n", "thing to do is to look at the groups separately.\n", "\n", "Looking at the accepted papers only shows a very different picture.\n", "There is very little correlation between accepted papers’ quality scores\n", "and the number of citations they receive." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "column = \"average_calibrated_quality\"\n", "filter_col = \"accept\"\n", "fig, ax = plt.subplots(figsize=plot.big_wide_figsize)\n", "plot.log_one_citations(column, joindf, filt=joindf[filter_col], ax=ax)\n", "ma.write_figure(filename=\"citations-vs-{col}-{filt}.svg\".format(filt=filter_col, col=column.replace(\"_\", \"-\")),\n", " directory=\"./neurips\")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "\n", "\n", "Figure: Scatter plot of $\\log_{10}(1+\\text{citations})$ against the\n", "average calibrated quality score for accepted papers. To prevent\n", "reidentification of individual papers quality scores and citation count,\n", "each point is corrupted by differentially private noise in the plot\n", "(correlation is computed before adding differentially private\n", "noise).\n", "\n", "Conversely, looking at rejected papers only, we do see a slight trend,\n", "with higher scoring papers achieving more citations on average. This,\n", "combined with the lower average number of citations in the rejected\n", "paper group, alongside their lower average scores, explains the\n", "correlation we originally observed." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "column = \"average_calibrated_quality\"\n", "filter_col = \"reject\"\n", "fig, ax = plt.subplots(figsize=plot.big_wide_figsize)\n", "plot.log_one_citations(column, joindf, filt=joindf[filter_col], ax=ax)\n", "ma.write_figure(filename=\"citations-vs-{col}-{filt}.svg\".format(filt=filter_col, col=column.replace(\"_\", \"-\")),\n", " directory=\"./neurips\")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "\n", "\n", "Figure: Scatter plot of $\\log_{10}(1+\\text{citations})$ against the\n", "average calibrated quality score for rejected papers. To prevent\n", "reidentification of individual papers quality scores and citation count,\n", "each point is corrupted by differentially private noise in the plot\n", "(correlation is computed before adding differentially private\n", "noise).\n", "\n", "Welling and Ghahramani introduced an “impact” score in NeurIPS 2013, we\n", "might expect the impact score to show correlation. And indeed, despite\n", "the lower range of the score (a reviewer can score either 1 or 2) we do\n", "see *some* correlation, although it is relatively weak." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "column = \"average_impact\"\n", "filter_col = \"accept\"\n", "fig, ax = plt.subplots(figsize=plot.big_wide_figsize)\n", "plot.log_one_citations(column, joindf, filt=joindf[filter_col], ax=ax)\n", "ma.write_figure(filename=\"citations-vs-{col}-{filt}.svg\".format(filt=filter_col, col=column.replace(\"_\", \"-\")),\n", " directory=\"./neurips\")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "\n", "\n", "Figure: Scatter plot of $\\log_{10}(1+\\text{citations})$ against the\n", "average impact score for accepted papers. To prevent reidentification of\n", "individual papers quality scores and citation count, each point is\n", "corrupted by differentially private noise in the plot (correlation is\n", "computed before adding differentially private noise).\n", "\n", "Finally, we also looked at correlation between the *confidence* score\n", "and the impact. Here correlation is somewhat stronger. Why should\n", "confidence be an indicator of higher citations? A plausible explanation\n", "is that there is confounder driving both variables. For example, it\n", "might be that papers which are easier to understand (due to elegance of\n", "the idea, or quality of exposition) inspire greater reviewer confidence\n", "and increase the number of citations." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "column = 'average_confidence'\n", "filter_col = \"accept\"\n", "fig, ax = plt.subplots(figsize=plot.big_wide_figsize)\n", "plot.log_one_citations(column, joindf, filt=joindf[filter_col], ax=ax)\n", "ma.write_figure(filename=\"citations-vs-{col}-{filt}.svg\".format(filt=filter_col, col=column.replace(\"_\", \"-\")),\n", " directory=\"./neurips\")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "\n", "\n", "Figure: Scatter plot of $\\log_{10}(1+\\text{citations})$ against the\n", "average confidence score for accepted papers. To prevent\n", "reidentification of individual papers quality scores and citation count,\n", "each point is corrupted by differentially private noise in the plot\n", "(correlation is computed before adding differentially private\n", "noise)." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "def bootstrap_index(df):\n", " n = len(df.index)\n", " return df.index[np.random.randint(n, size=n)]" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "for column in [\"average_quality\", \"average_impact\", \"average_confidence\"]:\n", " cor = []\n", " for i in range(1000):\n", " ind = bootstrap_index(joindf.loc[joindf.accept])\n", " cor.append(joindf.loc[ind][column].corr(np.log(1+joindf.loc[ind]['numCitedBy'])))\n", " cora = np.array(cor)\n", " rho = cora.mean()\n", " twosd = 2*np.sqrt(cora.var())\n", " print(\"{column}\".format(column=column.replace(\"_\", \" \")))\n", " print(\"Mean correlation is {rho} +/- {twosd}\".format(rho=rho, twosd=twosd))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Conclusion\n", "\n", "\\[edit\\]\n", "\n", "Under the simple model we have outlined, we can be confident that there\n", "is inconsistency between two independent committees, but the level of\n", "inconsistency is much less than we would find for a random committee. If\n", "we accept that the bias introduced by the Area Chairs knowing when they\n", "were dealing with duplicates was minimal, then if we were to revisit the\n", "NIPS 2014 conference with an independent committee then we would expect\n", "between **38% and 64% of the presented papers to be the same**. If the\n", "conference was run at random, then we would only expect 25% of the\n", "papers to be the same.\n", "\n", "It’s apparent from comments and speculation about what these results\n", "mean, that some people might be surprised by the size of this figure.\n", "However, it only requires a little thought to see that this figure is\n", "likely to be large for any highly selective conference if there is even\n", "a small amount of inconsistency in the decision-making process. This is\n", "because once the conference has chosen to be ‘highly selective’ then\n", "because, by definition, only a small percentage of papers are to be\n", "accepted. Now if we think of a type I error as accepting a paper which\n", "should be rejected, such errors are easier to make because, again by\n", "definition, many more papers should be rejected. Type II errors\n", "(rejecting a paper that should be accepted) are less likely because (by\n", "setting the accept rate low) there are fewer papers that should be\n", "accepted in the first place. When there is a difference of opinion\n", "between reviewers, it does seem that many of the aruguments can be\n", "distilled down to (a subjective opinion) about whether controlling for\n", "type I or type II errors is more important. Further, normally when\n", "discussing type I and type II errors we believe that the underlying\n", "system of study is genuinely binary: e.g., diseased or not diseased.\n", "However, for conferences the accept/reject boundary is not a clear\n", "separation point, there is a continuum (or spectrum) of paper quality\n", "(as there also is for some diseases). And the decision boundary often\n", "falls in a region of very high density.\n", "\n", "I would prefer a world were a conference is no longer viewed as a proxy\n", "for research quality. The true test of quality is time. In the current\n", "world, papers from conferences such as NeurIPS are being used to judge\n", "whether a researcher is worthy of a position at a leading company, or\n", "whether a researcher gets tenure. This is problematic and damaging for\n", "the community. Reviewing is an inconsistent process, but that is not a\n", "bad thing. It is far worse to have a reviewing system that is\n", "consistently wrong than one which is inconsistently wrong.\n", "\n", "My own view of a NeurIPS paper is inspired by the Millenium Galleries in\n", "Sheffield. There, among the exhibitions they sometimes have work done by\n", "apprentices in their ‘qualification.’ Sheffield is known for knives, and\n", "the work of the apprentices in making knives is sometimes very intricate\n", "indeed. But it does lead to some very impractical knives. NeurIPS seems\n", "to be good at judging technical skill, but not impact. And I suspect the\n", "same is true of many other meetings. So, a publication a NeurIPS does\n", "seem to indicate that the author has some of the skills required, but it\n", "does not necessarily imply that the paper will be impactful." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Thanks!\n", "\n", "For more information on these subjects and more you might want to check\n", "the following resources.\n", "\n", "- twitter: [@lawrennd](https://twitter.com/lawrennd)\n", "- podcast: [The Talking Machines](http://thetalkingmachines.com)\n", "- newspaper: [Guardian Profile\n", " Page](http://www.theguardian.com/profile/neil-lawrence)\n", "- blog:\n", " [http://inverseprobability.com](http://inverseprobability.com/blog.html)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## References" ] } ], "nbformat": 4, "nbformat_minor": 5, "metadata": {} }