Repository: cornellius-gp/gpytorch

Issue:
[Bug] PeriodicKernel returns error with BernoulliLikelihood 
# 🐛 Bug

When running the simple GPClassificationModel example (as in https://gpytorch.readthedocs.io/en/latest/examples/02_Simple_GP_Classification/Simple_GP_Classification.html), and changing the RBFKernel to a PeriodicKernel, the `likelihood(model(test_x))` line returns a RuntimeError:

`RuntimeError: The kernel LazyEvaluatedKernelTensor is not equipped to handle and diag. Expected size torch.Size([101]). Got size torch.Size([1, 101])`

This error does not occur when changing the RBFKernel to a LinearKernel, or when changing the BernoulliLikelihood to GaussianLikelihood.

## To reproduce

```
import math
import torch
import gpytorch
from matplotlib import pyplot as plt

train_x = torch.linspace(0, 1, 10)
train_y = torch.sign(torch.cos(train_x * (4 * math.pi))).add(1).div(2)

from gpytorch.models import AbstractVariationalGP
from gpytorch.variational import CholeskyVariationalDistribution
from gpytorch.variational import VariationalStrategy

class GPClassificationModel(AbstractVariationalGP):
    def __init__(self, train_x):
        variational_distribution = CholeskyVariationalDistribution(train_x.size(0))
        variational_strategy = VariationalStrategy(self, train_x, variational_distribution)
        super(GPClassificationModel, self).__init__(variational_strategy)
        self.mean_module = gpytorch.means.ConstantMean()
        self.covar_module = gpytorch.kernels.ScaleKernel(gpytorch.kernels.PeriodicKernel())

    def forward(self, x):
        mean_x = self.mean_module(x)
        covar_x = self.covar_module(x)
        latent_pred = gpytorch.distributions.MultivariateNormal(mean_x, covar_x)
        return latent_pred


model = GPClassificationModel(train_x)
likelihood = gpytorch.likelihoods.BernoulliLikelihood()

from gpytorch.mlls.variational_elbo import VariationalELBO

model.train()
likelihood.train()

optimizer = torch.optim.Adam(model.parameters(), lr=0.1)

mll = VariationalELBO(likelihood, model, train_y.numel())

training_iter = 50
for i in range(training_iter):
    optimizer.zero_grad()
    output = model(train_x)
    loss = -mll(output, train_y)
    loss.backward()
    optimizer.step()

model.eval()
likelihood.eval()

with torch.no_grad():
    test_x = torch.linspace(0, 1, 101)
    observed_pred = likelihood(model(test_x))

print('Reached the end of the script, successfully calculated likelihood')
```

** Stack trace/error message **
```
RuntimeError: The kernel LazyEvaluatedKernelTensor is not equipped to handle and diag. Expected size torch.Size([101]). Got size torch.Size([1, 101])
```

## Expected Behavior

The expected behavior is that the model will run with PeriodicKernel, and not only RBFKernel or LinearKernel.

## System information

**Please complete the following information:**
- <!-- GPyTorch Version (run `print(gpytorch.__version__)` -->0.3.2
- <!-- PyTorch Version (run `print(torch.__version__)` -->1.1.0
- <!-- Computer OS -->macOS



Discussion:
Thanks for reporting this, I'll look in to this tomorrow morning.
Update:
The issue occurs in regression with GaussianLikelihood as well, with sine wave as input.
Notice that the error doesn't occur when we reduce the input size from 300 to 200  samples.

```
import gpytorch
import math
import torch
from time import time
import copy

data_samples = 300 
# data_samples = 200 # Lowering the size of the input vector removes the problem
training_iterations = 100

class GPRegressionModel(gpytorch.models.ExactGP):
    def __init__(self, train_x, train_y, likelihood):
        super(GPRegressionModel, self).__init__(train_x, train_y, likelihood)
        self.mean_module = gpytorch.means.ConstantMean()
        self.covar_module = gpytorch.kernels.ScaleKernel(gpytorch.kernels.PeriodicKernel())

    def forward(self, x):
        mean_x = self.mean_module(x)
        covar_module = self.covar_module(x)
        return gpytorch.distributions.MultivariateNormal(mean_x, covar_module)

train_x = torch.linspace(0, 0.5, data_samples)
train_y = torch.sin(train_x * 100.0)

likelihood = gpytorch.likelihoods.GaussianLikelihood()
model = GPRegressionModel(train_x, train_y, likelihood)
mll = gpytorch.mlls.ExactMarginalLogLikelihood(likelihood, model)
optimizer = torch.optim.Adam([{'params': model.parameters()}], lr=0.1)
model.train()
likelihood.train()

optimizer.zero_grad()
output = model(train_x)
loss = -mll(output, train_y)
loss.backward()
optimizer.step()    
for i in range(training_iterations):
    optimizer.zero_grad()
    output = model(train_x)
    loss = -mll(output, train_y)
    loss.backward()
    print('Iter %d/%d - Loss: %.3f' % (i + 1, training_iterations, loss.item()))
    optimizer.step()

model.eval()
likelihood.eval()

with torch.no_grad():
    test_x = torch.linspace(0, train_x[-1] * 1, len(train_x) * 1)
    prediction = likelihood(model(test_x))
    mean = prediction.mean
    lower, upper = prediction.confidence_region()

```

Resolving patch:
diff --git a/gpytorch/kernels/periodic_kernel.py b/gpytorch/kernels/periodic_kernel.py
--- a/gpytorch/kernels/periodic_kernel.py
+++ b/gpytorch/kernels/periodic_kernel.py
@@ -102,11 +102,11 @@ def _set_period_length(self, value):
             value = torch.as_tensor(value).to(self.raw_period_length)
         self.initialize(raw_period_length=self.raw_period_length_constraint.inverse_transform(value))
 
-    def forward(self, x1, x2, **params):
+    def forward(self, x1, x2, diag=False, **params):
         x1_ = x1.div(self.period_length)
         x2_ = x2.div(self.period_length)
-        diff = self.covar_dist(x1_, x2_, **params)
+        diff = self.covar_dist(x1_, x2_, diag=diag, **params)
         res = torch.sin(diff.mul(math.pi)).pow(2).mul(-2 / self.lengthscale).exp_()
-        if diff.ndimension() == 2:
+        if diff.ndimension() == 2 or diag:
             res = res.squeeze(0)
         return res


Test patch: