# Source code for gpytorch.kernels.linear_kernel

#!/usr/bin/env python3

import warnings

import torch

from ..constraints import Positive
from ..lazy import MatmulLazyTensor, RootLazyTensor
from .kernel import Kernel

[docs]class LinearKernel(Kernel):
r"""
Computes a covariance matrix based on the Linear kernel
between inputs :math:\mathbf{x_1} and :math:\mathbf{x_2}:

.. math::
\begin{equation*}
k_\text{Linear}(\mathbf{x_1}, \mathbf{x_2}) = v\mathbf{x_1}^\top
\mathbf{x_2}.
\end{equation*}

where

* :math:v is a :attr:variance parameter.

.. note::

To implement this efficiently, we use a :obj:gpytorch.lazy.RootLazyTensor during training and a
:class:gpytorch.lazy.MatmulLazyTensor during test. These lazy tensors represent matrices of the form
:math:K = XX^{\top} and :math:K = XZ^{\top}. This makes inference
efficient because a matrix-vector product :math:Kv can be computed as
:math:Kv=X(X^{\top}v), where the base multiply :math:Xv takes only
:math:O(nd) time and space.

Args:
:attr:variance_prior (:class:gpytorch.priors.Prior):
Prior over the variance parameter (default None).
:attr:variance_constraint (Constraint, optional):
Constraint to place on variance parameter. Default: Positive.
:attr:active_dims (list):
List of data dimensions to operate on.
len(active_dims) should equal num_dimensions.
"""

def __init__(self, num_dimensions=None, offset_prior=None, variance_prior=None, variance_constraint=None, **kwargs):
super(LinearKernel, self).__init__(**kwargs)
if variance_constraint is None:
variance_constraint = Positive()

if num_dimensions is not None:
# Remove after 1.0
warnings.warn("The num_dimensions argument is deprecated and no longer used.", DeprecationWarning)
self.register_parameter(name="offset", parameter=torch.nn.Parameter(torch.zeros(1, 1, num_dimensions)))
if offset_prior is not None:
# Remove after 1.0
warnings.warn("The offset_prior argument is deprecated and no longer used.", DeprecationWarning)
self.register_parameter(name="raw_variance", parameter=torch.nn.Parameter(torch.zeros(*self.batch_shape, 1, 1)))
if variance_prior is not None:
self.register_prior(
"variance_prior", variance_prior, lambda: self.variance, lambda v: self._set_variance(v)
)

self.register_constraint("raw_variance", variance_constraint)

@property
def variance(self):
return self.raw_variance_constraint.transform(self.raw_variance)

@variance.setter
def variance(self, value):
self._set_variance(value)

def _set_variance(self, value):
if not torch.is_tensor(value):
value = torch.as_tensor(value).to(self.raw_variance)
self.initialize(raw_variance=self.raw_variance_constraint.inverse_transform(value))

def forward(self, x1, x2, diag=False, last_dim_is_batch=False, **params):
x1_ = x1 * self.variance.sqrt()
if last_dim_is_batch:
x1_ = x1_.transpose(-1, -2).unsqueeze(-1)

if x1.size() == x2.size() and torch.equal(x1, x2):
# Use RootLazyTensor when x1 == x2 for efficiency when composing
# with other kernels
prod = RootLazyTensor(x1_)

else:
x2_ = x2 * self.variance.sqrt()
if last_dim_is_batch:
x2_ = x2_.transpose(-1, -2).unsqueeze(-1)

prod = MatmulLazyTensor(x1_, x2_.transpose(-2, -1))

if diag:
return prod.diag()
else:
return prod