diff --git a/gpytorch/likelihoods/__init__.py b/gpytorch/likelihoods/__init__.py
index ccee934d..5f364474 100644
--- a/gpytorch/likelihoods/__init__.py
+++ b/gpytorch/likelihoods/__init__.py
@@ -1,7 +1,9 @@
 #!/usr/bin/env python3
 
 from .bernoulli_likelihood import BernoulliLikelihood
+from .beta_likelihood import BetaLikelihood
 from .gaussian_likelihood import FixedNoiseGaussianLikelihood, GaussianLikelihood, _GaussianLikelihoodBase
+from .laplace_likelihood import LaplaceLikelihood
 from .likelihood import Likelihood, _OneDimensionalLikelihood
 from .likelihood_list import LikelihoodList
 from .multitask_gaussian_likelihood import (
@@ -11,18 +13,22 @@ from .multitask_gaussian_likelihood import (
 )
 from .noise_models import HeteroskedasticNoise
 from .softmax_likelihood import SoftmaxLikelihood
+from .student_t_likelihood import StudentTLikelihood
 
 __all__ = [
     "_GaussianLikelihoodBase",
     "_OneDimensionalLikelihood",
     "_MultitaskGaussianLikelihoodBase",
     "BernoulliLikelihood",
+    "BetaLikelihood",
     "FixedNoiseGaussianLikelihood",
     "GaussianLikelihood",
     "HeteroskedasticNoise",
+    "LaplaceLikelihood",
     "Likelihood",
     "LikelihoodList",
     "MultitaskGaussianLikelihood",
     "MultitaskGaussianLikelihoodKronecker",
     "SoftmaxLikelihood",
+    "StudentTLikelihood",
 ]
diff --git a/gpytorch/likelihoods/beta_likelihood.py b/gpytorch/likelihoods/beta_likelihood.py
new file mode 100644
index 00000000..e3a97483
--- /dev/null
+++ b/gpytorch/likelihoods/beta_likelihood.py
@@ -0,0 +1,59 @@
+#!/usr/bin/env python3
+
+import torch
+
+from ..constraints import Positive
+from ..distributions import base_distributions
+from .likelihood import _OneDimensionalLikelihood
+
+
+class BetaLikelihood(_OneDimensionalLikelihood):
+    r"""
+    A Beta likelihood for regressing over percentages.
+
+    The Beta distribution is parameterized by :math:`\alpha > 0` and :math:`\beta > 0` parameters
+    which roughly correspond to the number of prior positive and negative observations.
+    We instead parameterize it through a mixture :math:`m \in [0, 1]` and scale :math:`s > 0` parameter.
+
+    .. math::
+        \begin{equation*}
+            \alpha = ms, \quad \beta = (1-m)s
+        \end{equation*}
+
+    The mixture parameter is the output of the GP passed through a logit function.
+    The scale parameter is learned.
+
+    :var torch.Tensor scale: :math:`s` parameter (scale)
+    """
+
+    def __init__(self, batch_shape=torch.Size([]), scale_prior=None, scale_constraint=None):
+        super().__init__()
+
+        if scale_constraint is None:
+            scale_constraint = Positive()
+
+        self.raw_scale = torch.nn.Parameter(torch.ones(*batch_shape, 1))
+        if scale_prior is not None:
+            self.register_prior("scale_prior", scale_prior, lambda: self.scale, lambda v: self._set_scale(v))
+
+        self.register_constraint("raw_scale", scale_constraint)
+
+    @property
+    def scale(self):
+        return self.raw_scale_constraint.transform(self.raw_scale)
+
+    @scale.setter
+    def scale(self, value):
+        self._set_scale(value)
+
+    def _set_scale(self, value):
+        if not torch.is_tensor(value):
+            value = torch.as_tensor(value).to(self.raw_scale)
+        self.initialize(raw_scale=self.raw_scale_constraint.inverse_transform(value))
+
+    def forward(self, function_samples, **kwargs):
+        mixture = torch.sigmoid(function_samples)
+        scale = self.scale
+        alpha = mixture * scale + 1
+        beta = scale - alpha + 2
+        return base_distributions.Beta(concentration1=alpha, concentration0=beta)
diff --git a/gpytorch/likelihoods/laplace_likelihood.py b/gpytorch/likelihoods/laplace_likelihood.py
new file mode 100644
index 00000000..3a7736cd
--- /dev/null
+++ b/gpytorch/likelihoods/laplace_likelihood.py
@@ -0,0 +1,45 @@
+#!/usr/bin/env python3
+
+import torch
+
+from ..constraints import Positive
+from ..distributions import base_distributions
+from .likelihood import _OneDimensionalLikelihood
+
+
+class LaplaceLikelihood(_OneDimensionalLikelihood):
+    r"""
+    A Laplace likelihood/noise model for GP regression.
+    It has one learnable parameter: :math:`\sigma^2` - the noise
+
+    :var torch.Tensor noise: :math:`\sigma^2` parameter (noise)
+    """
+
+    def __init__(self, batch_shape=torch.Size([]), noise_prior=None, noise_constraint=None):
+        super().__init__()
+
+        if noise_constraint is None:
+            noise_constraint = Positive()
+
+        self.raw_noise = torch.nn.Parameter(torch.zeros(*batch_shape, 1))
+
+        if noise_prior is not None:
+            self.register_prior("noise_prior", noise_prior, lambda: self.noise, lambda v: self._set_noise(v))
+
+        self.register_constraint("raw_noise", noise_constraint)
+
+    @property
+    def noise(self):
+        return self.raw_noise_constraint.transform(self.raw_noise)
+
+    @noise.setter
+    def noise(self, value):
+        self._set_noise(value)
+
+    def _set_noise(self, value):
+        if not torch.is_tensor(value):
+            value = torch.as_tensor(value).to(self.raw_noise)
+        self.initialize(raw_noise=self.raw_noise_constraint.inverse_transform(value))
+
+    def forward(self, function_samples, **kwargs):
+        return base_distributions.Laplace(loc=function_samples, scale=self.noise.sqrt())
diff --git a/gpytorch/likelihoods/student_t_likelihood.py b/gpytorch/likelihoods/student_t_likelihood.py
new file mode 100644
index 00000000..2b402141
--- /dev/null
+++ b/gpytorch/likelihoods/student_t_likelihood.py
@@ -0,0 +1,81 @@
+#!/usr/bin/env python3
+
+import torch
+
+from ..constraints import GreaterThan, Positive
+from ..distributions import base_distributions
+from .likelihood import _OneDimensionalLikelihood
+
+
+class StudentTLikelihood(_OneDimensionalLikelihood):
+    r"""
+    A Student T likelihood/noise model for GP regression.
+    It has two learnable parameters: :math:`\nu` - the degrees of freedom, and
+    :math:`\sigma^2` - the noise
+
+    :var torch.Tensor deg_free: :math:`\nu` parameter (degrees of freedom)
+    :var torch.Tensor noise: :math:`\sigma^2` parameter (noise)
+    """
+
+    def __init__(
+        self,
+        batch_shape: torch.Size = torch.Size([]),
+        deg_free_prior=None,
+        deg_free_constraint=None,
+        noise_prior=None,
+        noise_constraint=None,
+    ):
+        super().__init__()
+
+        if deg_free_constraint is None:
+            deg_free_constraint = GreaterThan(2)
+
+        if noise_constraint is None:
+            noise_constraint = Positive()
+
+        self.raw_deg_free = torch.nn.Parameter(torch.zeros(*batch_shape, 1))
+        self.raw_noise = torch.nn.Parameter(torch.zeros(*batch_shape, 1))
+
+        if noise_prior is not None:
+            self.register_prior("noise_prior", noise_prior, lambda: self.noise, lambda v: self._set_noise(v))
+
+        self.register_constraint("raw_noise", noise_constraint)
+
+        if deg_free_prior is not None:
+            self.register_prior(
+                "deg_free_prior", deg_free_prior, lambda: self.deg_free, lambda v: self._set_deg_free(v)
+            )
+
+        self.register_constraint("raw_deg_free", deg_free_constraint)
+
+        # Rough initialization
+        self.initialize(deg_free=7)
+
+    @property
+    def deg_free(self):
+        return self.raw_deg_free_constraint.transform(self.raw_deg_free)
+
+    @deg_free.setter
+    def deg_free(self, value):
+        self._set_deg_free(value)
+
+    def _set_deg_free(self, value):
+        if not torch.is_tensor(value):
+            value = torch.as_tensor(value).to(self.raw_deg_free)
+        self.initialize(raw_deg_free=self.raw_deg_free_constraint.inverse_transform(value))
+
+    @property
+    def noise(self):
+        return self.raw_noise_constraint.transform(self.raw_noise)
+
+    @noise.setter
+    def noise(self, value):
+        self._set_noise(value)
+
+    def _set_noise(self, value):
+        if not torch.is_tensor(value):
+            value = torch.as_tensor(value).to(self.raw_noise)
+        self.initialize(raw_noise=self.raw_noise_constraint.inverse_transform(value))
+
+    def forward(self, function_samples, **kwargs):
+        return base_distributions.StudentT(df=self.deg_free, loc=function_samples, scale=self.noise.sqrt())

Logit transform and fixed noise gaussian likelihood
Dear gpytorch developers,

This a probably basic question rather than a documentation/examples question. I removed the template in order to acomodate my question better. I hope it is fine

 I have an issue using logit transformed data and a fixed noise gaussian likelihood:

**My setting**

I have a set of training observations `train_x` and `train_y` , and I have known noise for the `train_y`.  I know the observation noise associated to to the training inputs, and the noise is a bit heterocedastic. Thus, I am using a FixedNoiseGaussianLikelihood with a vector  `my_noise` specifing the input noise. I simply implement this as follows:

```
likelihood = gpytorch.likelihoods.FixedNoiseGaussianLikelihood(noise=my_noise)
model = ExactGPMode(train_x,train_y,likelihood)
```

The rest of the model is pretty much the same as the Spectral mixture kernel notebook

**The problem**

My `train_y` data lies in the interval (0,0.5], in and has a lot of probability density close to 0 and 0.5. In order to account for this, I logit transform `train_y` before to feeding it to the gpytorch engine. However, due to the non-linear transformation of the logit, I cannot directly pass the noise vector as:
'
$logit(y+noise) != logit(y) + logit(noise)$

Is there a way to deal with this inside the gpytorch engine?


Having said that, thanks a lot for this great library. Makes implementing gaussian processes very easy to people!

Best,

Inigo

diff --git a/test/likelihoods/test_beta_likelihood.py b/test/likelihoods/test_beta_likelihood.py
new file mode 100644
index 00000000..cae4a93e
--- /dev/null
+++ b/test/likelihoods/test_beta_likelihood.py
@@ -0,0 +1,45 @@
+#!/usr/bin/env python3
+
+import unittest
+
+import torch
+
+from gpytorch.likelihoods import BetaLikelihood, _OneDimensionalLikelihood
+from gpytorch.test.base_likelihood_test_case import BaseLikelihoodTestCase
+
+
+class TestBetaLikelihood(BaseLikelihoodTestCase, unittest.TestCase):
+    seed = 1
+
+    def _create_targets(self, batch_shape=torch.Size([])):
+        res = torch.sigmoid(torch.randn(*batch_shape, 5)).float()
+        return res
+
+    def create_likelihood(self, **kwargs):
+        return BetaLikelihood(**kwargs)
+
+    def _test_log_marginal(self, batch_shape):
+        likelihood = self.create_likelihood()
+        input = self._create_marginal_input(batch_shape)
+        target = self._create_targets(batch_shape)
+        output = likelihood.log_marginal(target, input)
+
+        self.assertTrue(torch.is_tensor(output))
+        self.assertEqual(output.shape, batch_shape + torch.Size([5]))
+        default_log_prob = _OneDimensionalLikelihood.log_marginal(likelihood, target, input)
+        self.assertAllClose(output.sum(-1), default_log_prob.sum(-1), rtol=0.25, atol=0.1)
+
+    def _test_log_prob(self, batch_shape):
+        likelihood = self.create_likelihood()
+        input = self._create_marginal_input(batch_shape)
+        target = self._create_targets(batch_shape)
+        output = likelihood.expected_log_prob(target, input)
+
+        self.assertTrue(torch.is_tensor(output))
+        self.assertEqual(output.shape, batch_shape + torch.Size([5]))
+        default_log_prob = _OneDimensionalLikelihood.expected_log_prob(likelihood, target, input)
+        self.assertAllClose(output.sum(-1), default_log_prob.sum(-1), rtol=0.25, atol=0.1)
+
+    def _test_marginal(self, batch_shape):
+        # Likelihood uses the default marginal behavior, no point testing a set of samples against a set of samples.
+        pass
diff --git a/test/likelihoods/test_laplace_likelihood.py b/test/likelihoods/test_laplace_likelihood.py
new file mode 100644
index 00000000..ff8974ce
--- /dev/null
+++ b/test/likelihoods/test_laplace_likelihood.py
@@ -0,0 +1,45 @@
+#!/usr/bin/env python3
+
+import unittest
+
+import torch
+
+from gpytorch.likelihoods import LaplaceLikelihood, _OneDimensionalLikelihood
+from gpytorch.test.base_likelihood_test_case import BaseLikelihoodTestCase
+
+
+class TestLaplaceLikelihood(BaseLikelihoodTestCase, unittest.TestCase):
+    seed = 1
+
+    def _create_targets(self, batch_shape=torch.Size([])):
+        res = torch.sigmoid(torch.randn(*batch_shape, 5)).float()
+        return res
+
+    def create_likelihood(self, **kwargs):
+        return LaplaceLikelihood(**kwargs)
+
+    def _test_log_marginal(self, batch_shape):
+        likelihood = self.create_likelihood()
+        input = self._create_marginal_input(batch_shape)
+        target = self._create_targets(batch_shape)
+        output = likelihood.log_marginal(target, input)
+
+        self.assertTrue(torch.is_tensor(output))
+        self.assertEqual(output.shape, batch_shape + torch.Size([5]))
+        default_log_prob = _OneDimensionalLikelihood.log_marginal(likelihood, target, input)
+        self.assertAllClose(output.sum(-1), default_log_prob.sum(-1), rtol=0.25, atol=0.1)
+
+    def _test_log_prob(self, batch_shape):
+        likelihood = self.create_likelihood()
+        input = self._create_marginal_input(batch_shape)
+        target = self._create_targets(batch_shape)
+        output = likelihood.expected_log_prob(target, input)
+
+        self.assertTrue(torch.is_tensor(output))
+        self.assertEqual(output.shape, batch_shape + torch.Size([5]))
+        default_log_prob = _OneDimensionalLikelihood.expected_log_prob(likelihood, target, input)
+        self.assertAllClose(output.sum(-1), default_log_prob.sum(-1), rtol=0.25, atol=0.1)
+
+    def _test_marginal(self, batch_shape):
+        # Likelihood uses the default marginal behavior, no point testing a set of samples against a set of samples.
+        pass
diff --git a/test/likelihoods/test_student_t_likelihood.py b/test/likelihoods/test_student_t_likelihood.py
new file mode 100644
index 00000000..41e5929e
--- /dev/null
+++ b/test/likelihoods/test_student_t_likelihood.py
@@ -0,0 +1,45 @@
+#!/usr/bin/env python3
+
+import unittest
+
+import torch
+
+from gpytorch.likelihoods import StudentTLikelihood, _OneDimensionalLikelihood
+from gpytorch.test.base_likelihood_test_case import BaseLikelihoodTestCase
+
+
+class TestStudentTLikelihood(BaseLikelihoodTestCase, unittest.TestCase):
+    seed = 1
+
+    def _create_targets(self, batch_shape=torch.Size([])):
+        res = torch.sigmoid(torch.randn(*batch_shape, 5)).float()
+        return res
+
+    def create_likelihood(self, **kwargs):
+        return StudentTLikelihood(**kwargs)
+
+    def _test_log_marginal(self, batch_shape):
+        likelihood = self.create_likelihood()
+        input = self._create_marginal_input(batch_shape)
+        target = self._create_targets(batch_shape)
+        output = likelihood.log_marginal(target, input)
+
+        self.assertTrue(torch.is_tensor(output))
+        self.assertEqual(output.shape, batch_shape + torch.Size([5]))
+        default_log_prob = _OneDimensionalLikelihood.log_marginal(likelihood, target, input)
+        self.assertAllClose(output.sum(-1), default_log_prob.sum(-1), rtol=0.25, atol=0.1)
+
+    def _test_log_prob(self, batch_shape):
+        likelihood = self.create_likelihood()
+        input = self._create_marginal_input(batch_shape)
+        target = self._create_targets(batch_shape)
+        output = likelihood.expected_log_prob(target, input)
+
+        self.assertTrue(torch.is_tensor(output))
+        self.assertEqual(output.shape, batch_shape + torch.Size([5]))
+        default_log_prob = _OneDimensionalLikelihood.expected_log_prob(likelihood, target, input)
+        self.assertAllClose(output.sum(-1), default_log_prob.sum(-1), rtol=0.25, atol=0.1)
+
+    def _test_marginal(self, batch_shape):
+        # Likelihood uses the default marginal behavior, no point testing a set of samples against a set of samples.
+        pass

pip install -e .[dev]
parse_log_pytest
requirements.txt
pytest nbval
3.6
requirements.txt
pytest --no-header -rA --tb=line --color=no -p no:cacheprovider -W ignore::DeprecationWarning
attrs==22.2.0
backcall==0.2.0
black==22.8.0
bleach==4.1.0
certifi==2021.5.30
cffi==1.15.1
cfgv==3.3.1
charset-normalizer==2.0.12
click==8.0.4
colorama==0.4.5
coverage==6.2
cryptography==40.0.2
dataclasses==0.8
decorator==5.1.1
distlib==0.3.9
docutils==0.18.1
entrypoints==0.4
filelock==3.4.1
-e git+https://github.com/cornellius-gp/gpytorch.git@0a1e0d601635d16cdc471fa9a3c8ebabbe04d187#egg=gpytorch
identify==2.4.4
idna==3.10
importlib-metadata==4.8.3
importlib-resources==5.2.3
iniconfig==1.1.1
ipykernel==5.5.6
ipython==7.16.3
ipython-genutils==0.2.0
jedi==0.17.2
jeepney==0.7.1
joblib==1.1.1
jsonschema==3.2.0
jupyter-client==7.1.2
jupyter-core==4.9.2
keyring==23.4.1
mypy-extensions==1.0.0
nbformat==5.1.3
nbval==0.10.0
nest-asyncio==1.6.0
nodeenv==1.6.0
numpy==1.19.5
packaging==21.3
parso==0.7.1
pathspec==0.9.0
pexpect==4.9.0
pickleshare==0.7.5
pkginfo==1.10.0
platformdirs==2.4.0
pluggy==1.0.0
pre-commit==2.17.0
prompt-toolkit==3.0.36
ptyprocess==0.7.0
py==1.11.0
pycparser==2.21
Pygments==2.14.0
pyparsing==3.1.4
pyrsistent==0.18.0
pytest==7.0.1
python-dateutil==2.9.0.post0
PyYAML==6.0.1
pyzmq==25.1.2
readme-renderer==34.0
requests==2.27.1
requests-toolbelt==1.0.0
rfc3986==1.5.0
scikit-learn==0.24.2
scipy==1.5.4
SecretStorage==3.3.3
six==1.17.0
threadpoolctl==3.1.0
toml==0.10.2
tomli==1.2.3
torch==1.10.2
tornado==6.1
tqdm==4.64.1
traitlets==4.3.3
twine==3.8.0
typed-ast==1.5.5
typing_extensions==4.1.1
urllib3==1.26.20
virtualenv==20.16.2
wcwidth==0.2.13
webencodings==0.5.1
zipp==3.6.0

name: gpytorch
channels:
  - defaults
  - https://repo.anaconda.com/pkgs/main
  - https://repo.anaconda.com/pkgs/r
  - conda-forge
dependencies:
  - _libgcc_mutex=0.1=main
  - _openmp_mutex=5.1=1_gnu
  - ca-certificates=2025.2.25=h06a4308_0
  - certifi=2021.5.30=py36h06a4308_0
  - ld_impl_linux-64=2.40=h12ee557_0
  - libffi=3.3=he6710b0_2
  - libgcc-ng=11.2.0=h1234567_1
  - libgomp=11.2.0=h1234567_1
  - libstdcxx-ng=11.2.0=h1234567_1
  - ncurses=6.4=h6a678d5_0
  - openssl=1.1.1w=h7f8727e_0
  - pip=21.2.2=py36h06a4308_0
  - python=3.6.13=h12debd9_1
  - readline=8.2=h5eee18b_0
  - setuptools=58.0.4=py36h06a4308_0
  - sqlite=3.45.3=h5eee18b_0
  - tk=8.6.14=h39e8969_0
  - wheel=0.37.1=pyhd3eb1b0_0
  - xz=5.6.4=h5eee18b_1
  - zlib=1.2.13=h5eee18b_1
  - pip:
      - attrs==22.2.0
      - backcall==0.2.0
      - black==22.8.0
      - bleach==4.1.0
      - cffi==1.15.1
      - cfgv==3.3.1
      - charset-normalizer==2.0.12
      - click==8.0.4
      - colorama==0.4.5
      - coverage==6.2
      - cryptography==40.0.2
      - dataclasses==0.8
      - decorator==5.1.1
      - distlib==0.3.9
      - docutils==0.18.1
      - entrypoints==0.4
      - filelock==3.4.1
      - identify==2.4.4
      - idna==3.10
      - importlib-metadata==4.8.3
      - importlib-resources==5.2.3
      - iniconfig==1.1.1
      - ipykernel==5.5.6
      - ipython==7.16.3
      - ipython-genutils==0.2.0
      - jedi==0.17.2
      - jeepney==0.7.1
      - joblib==1.1.1
      - jsonschema==3.2.0
      - jupyter-client==7.1.2
      - jupyter-core==4.9.2
      - keyring==23.4.1
      - mypy-extensions==1.0.0
      - nbformat==5.1.3
      - nbval==0.10.0
      - nest-asyncio==1.6.0
      - nodeenv==1.6.0
      - numpy==1.19.5
      - packaging==21.3
      - parso==0.7.1
      - pathspec==0.9.0
      - pexpect==4.9.0
      - pickleshare==0.7.5
      - pkginfo==1.10.0
      - platformdirs==2.4.0
      - pluggy==1.0.0
      - pre-commit==2.17.0
      - prompt-toolkit==3.0.36
      - ptyprocess==0.7.0
      - py==1.11.0
      - pycparser==2.21
      - pygments==2.14.0
      - pyparsing==3.1.4
      - pyrsistent==0.18.0
      - pytest==7.0.1
      - python-dateutil==2.9.0.post0
      - pyyaml==6.0.1
      - pyzmq==25.1.2
      - readme-renderer==34.0
      - requests==2.27.1
      - requests-toolbelt==1.0.0
      - rfc3986==1.5.0
      - scikit-learn==0.24.2
      - scipy==1.5.4
      - secretstorage==3.3.3
      - six==1.17.0
      - threadpoolctl==3.1.0
      - toml==0.10.2
      - tomli==1.2.3
      - torch==1.10.2
      - tornado==6.1
      - tqdm==4.64.1
      - traitlets==4.3.3
      - twine==3.8.0
      - typed-ast==1.5.5
      - typing-extensions==4.1.1
      - urllib3==1.26.20
      - virtualenv==20.16.2
      - wcwidth==0.2.13
      - webencodings==0.5.1
      - zipp==3.6.0
prefix: /opt/conda/envs/gpytorch

test/likelihoods/test_beta_likelihood.py::TestBetaLikelihood::test_batch test/likelihoods/test_beta_likelihood.py::TestBetaLikelihood::test_multi_batch test/likelihoods/test_beta_likelihood.py::TestBetaLikelihood::test_nonbatch test/likelihoods/test_laplace_likelihood.py::TestLaplaceLikelihood::test_batch test/likelihoods/test_laplace_likelihood.py::TestLaplaceLikelihood::test_multi_batch test/likelihoods/test_laplace_likelihood.py::TestLaplaceLikelihood::test_nonbatch test/likelihoods/test_student_t_likelihood.py::TestStudentTLikelihood::test_batch test/likelihoods/test_student_t_likelihood.py::TestStudentTLikelihood::test_multi_batch test/likelihoods/test_student_t_likelihood.py::TestStudentTLikelihood::test_nonbatch
gpytorch/likelihoods/laplace_likelihood.py gpytorch/likelihoods/__init__.py gpytorch/likelihoods/beta_likelihood.py gpytorch/likelihoods/student_t_likelihood.py
gpytorch/likelihoods/laplace_likelihood.py gpytorch/likelihoods/__init__.py gpytorch/likelihoods/beta_likelihood.py gpytorch/likelihoods/student_t_likelihood.py