diff --git a/optgbm/sklearn.py b/optgbm/sklearn.py
index 21102e1..33b559f 100644
--- a/optgbm/sklearn.py
+++ b/optgbm/sklearn.py
@@ -23,8 +23,12 @@ from sklearn.utils.validation import check_is_fitted
 
 try:  # lightgbm<=2.2.3
     from lightgbm.sklearn import _eval_function_wrapper as _EvalFunctionWrapper
+    from lightgbm.sklearn import (
+        _objective_function_wrapper as _ObjectiveFunctionWrapper,
+    )
 except ImportError:
     from lightgbm.sklearn import _EvalFunctionWrapper
+    from lightgbm.sklearn import _ObjectiveFunctionWrapper
 
 from .utils import check_cv
 from .utils import check_fit_params
@@ -116,6 +120,7 @@ class _Objective(object):
         enable_pruning: bool = False,
         feature_name: Union[List[str], str] = "auto",
         feval: Optional[Callable] = None,
+        fobj: Optional[Callable] = None,
         n_estimators: int = 100,
     ) -> None:
         self.callbacks = callbacks
@@ -125,8 +130,9 @@ class _Objective(object):
         self.early_stopping_rounds = early_stopping_rounds
         self.enable_pruning = enable_pruning
         self.eval_name = eval_name
-        self.feval = feval
         self.feature_name = feature_name
+        self.feval = feval
+        self.fobj = fobj
         self.is_higher_better = is_higher_better
         self.n_estimators = n_estimators
         self.params = params
@@ -144,6 +150,7 @@ class _Objective(object):
             early_stopping_rounds=self.early_stopping_rounds,
             feature_name=self.feature_name,
             feval=self.feval,
+            fobj=self.fobj,
             folds=self.cv,
             num_boost_round=self.n_estimators,
         )
@@ -262,7 +269,7 @@ class _BaseOGBMModel(lgb.LGBMModel):
         learning_rate: float = 0.1,
         n_estimators: int = 1_000,
         subsample_for_bin: int = 200_000,
-        objective: Optional[str] = None,
+        objective: Optional[Union[Callable, str]] = None,
         class_weight: Optional[Union[Dict[str, float], str]] = None,
         min_split_gain: float = 0.0,
         min_child_weight: float = 1e-03,
@@ -319,15 +326,15 @@ class _BaseOGBMModel(lgb.LGBMModel):
         check_is_fitted(self, "n_features_")
 
     def _get_objective(self) -> str:
-        if self.objective is None:
-            if self._n_classes is None:
-                return "regression"
-            elif self._n_classes > 2:
-                return "multiclass"
-            else:
-                return "binary"
+        if isinstance(self.objective, str):
+            return self.objective
 
-        return self.objective
+        if self._n_classes is None:
+            return "regression"
+        elif self._n_classes > 2:
+            return "multiclass"
+        else:
+            return "binary"
 
     def _get_param_distributions(
         self,
@@ -432,6 +439,11 @@ class _BaseOGBMModel(lgb.LGBMModel):
             if self._n_classes > 2:
                 params["num_classes"] = self._n_classes
 
+        if callable(self.objective):
+            fobj = _ObjectiveFunctionWrapper(self.objective)
+        else:
+            fobj = None
+
         self._objective = self._get_objective()
 
         params["objective"] = self._objective
@@ -477,6 +489,7 @@ class _BaseOGBMModel(lgb.LGBMModel):
             enable_pruning=self.enable_pruning,
             feature_name=feature_name,
             feval=feval,
+            fobj=fobj,
             n_estimators=self.n_estimators,
         )
 
@@ -838,7 +851,7 @@ class OGBMRegressor(_BaseOGBMModel, RegressorMixin):
         learning_rate: float = 0.1,
         n_estimators: int = 1_000,
         subsample_for_bin: int = 200_000,
-        objective: Optional[str] = None,
+        objective: Optional[Union[Callable, str]] = None,
         min_split_gain: float = 0.0,
         min_child_weight: float = 1e-03,
         min_child_samples: int = 20,

Allow objective to be a callable
diff --git a/tests/test_sklearn.py b/tests/test_sklearn.py
index 99a569c..2bff245 100644
--- a/tests/test_sklearn.py
+++ b/tests/test_sklearn.py
@@ -9,7 +9,6 @@ import numpy as np
 import optuna
 import pytest
 
-from sklearn.datasets import load_boston
 from sklearn.datasets import load_breast_cancer
 from sklearn.datasets import load_digits
 from sklearn.datasets import load_iris
@@ -27,6 +26,14 @@ callback = lgb.reset_parameter(
 )
 
 
+def log_likelihood(
+    y_true: np.ndarray, y_pred: np.ndarray
+) -> Tuple[np.ndarray, np.ndarray]:
+    y_pred = 1.0 / (1.0 + np.exp(-y_pred))
+
+    return y_pred - y_true, y_pred * (1.0 - y_pred)
+
+
 def zero_one_loss(
     y_true: np.ndarray, y_pred: np.ndarray
 ) -> Tuple[str, float, bool]:
@@ -51,13 +58,16 @@ def test_ogbm_regressor() -> None:
     check_set_params(name, reg)
 
 
-@pytest.mark.parametrize("reg_sqrt", [False, True])
-def test_fit_with_params(reg_sqrt: bool) -> None:
-    X, y = load_boston(return_X_y=True)
+@pytest.mark.parametrize("is_unbalance", [False, True])
+@pytest.mark.parametrize("objective", [None, log_likelihood])
+def test_fit_with_params(
+    is_unbalance: bool, objective: Optional[Union[Callable, str]],
+) -> None:
+    X, y = load_breast_cancer(return_X_y=True)
 
-    reg = OGBMRegressor(reg_sqrt=reg_sqrt)
+    clf = OGBMClassifier(is_unbalance=is_unbalance, objective=objective)
 
-    reg.fit(X, y)
+    clf.fit(X, y)
 
 
 @pytest.mark.parametrize("callbacks", [None, [callback]])

pip install -e .[develop]
parse_log_pytest
pytest
pytest pytest-flake8 pytest-mypy pytest-pydocstyle
3.7
pytest --no-header -rA --tb=line --color=no -p no:cacheprovider -W ignore::DeprecationWarning
alembic==1.12.1
attrs @ file:///croot/attrs_1668696182826/work
certifi @ file:///croot/certifi_1671487769961/work/certifi
colorlog==6.9.0
cycler==0.11.0
filelock==3.12.2
flake8==5.0.4
flit_core @ file:///opt/conda/conda-bld/flit-core_1644941570762/work/source/flit_core
fonttools==4.38.0
greenlet==3.1.1
importlib-metadata==4.2.0
importlib-resources==5.12.0
iniconfig @ file:///home/linux1/recipes/ci/iniconfig_1610983019677/work
joblib==1.3.2
kiwisolver==1.4.5
lightgbm==4.6.0
Mako==1.2.4
MarkupSafe==2.1.5
matplotlib==3.5.3
mccabe==0.7.0
mypy==1.4.1
mypy-extensions==1.0.0
numpy==1.21.6
-e git+https://github.com/Y-oHr-N/OptGBM.git@f258eba11c9433ff1dc196e842010cc24d31547b#egg=OptGBM
optuna==4.0.0
packaging @ file:///croot/packaging_1671697413597/work
pandas==1.3.5
Pillow==9.5.0
pluggy @ file:///tmp/build/80754af9/pluggy_1648042572264/work
py @ file:///opt/conda/conda-bld/py_1644396412707/work
pycodestyle==2.9.1
pydocstyle==6.3.0
pyflakes==2.5.0
pyparsing==3.1.4
pytest==7.1.2
pytest-flake8==1.1.3
pytest-mypy==0.10.3
pytest-pydocstyle==2.3.2
python-dateutil==2.9.0.post0
pytz==2025.2
PyYAML==6.0.1
scikit-learn==1.0.2
scipy==1.7.3
six==1.17.0
snowballstemmer==2.2.0
SQLAlchemy==2.0.40
threadpoolctl==3.1.0
tomli @ file:///opt/conda/conda-bld/tomli_1657175507142/work
tqdm==4.67.1
typed-ast==1.5.5
typing_extensions==4.7.1
zipp @ file:///croot/zipp_1672387121353/work

name: OptGBM
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
  - attrs=22.1.0=py37h06a4308_0
  - ca-certificates=2025.2.25=h06a4308_0
  - certifi=2022.12.7=py37h06a4308_0
  - flit-core=3.6.0=pyhd3eb1b0_0
  - importlib_metadata=4.11.3=hd3eb1b0_0
  - iniconfig=1.1.1=pyhd3eb1b0_0
  - ld_impl_linux-64=2.40=h12ee557_0
  - libffi=3.4.4=h6a678d5_1
  - 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
  - packaging=22.0=py37h06a4308_0
  - pip=22.3.1=py37h06a4308_0
  - pluggy=1.0.0=py37h06a4308_1
  - py=1.11.0=pyhd3eb1b0_0
  - pytest=7.1.2=py37h06a4308_0
  - python=3.7.16=h7a1cb2a_0
  - readline=8.2=h5eee18b_0
  - setuptools=65.6.3=py37h06a4308_0
  - sqlite=3.45.3=h5eee18b_0
  - tk=8.6.14=h39e8969_0
  - tomli=2.0.1=py37h06a4308_0
  - wheel=0.38.4=py37h06a4308_0
  - xz=5.6.4=h5eee18b_1
  - zipp=3.11.0=py37h06a4308_0
  - zlib=1.2.13=h5eee18b_1
  - pip:
      - alembic==1.12.1
      - colorlog==6.9.0
      - cycler==0.11.0
      - filelock==3.12.2
      - flake8==5.0.4
      - fonttools==4.38.0
      - greenlet==3.1.1
      - importlib-metadata==4.2.0
      - importlib-resources==5.12.0
      - joblib==1.3.2
      - kiwisolver==1.4.5
      - lightgbm==4.6.0
      - mako==1.2.4
      - markupsafe==2.1.5
      - matplotlib==3.5.3
      - mccabe==0.7.0
      - mypy==1.4.1
      - mypy-extensions==1.0.0
      - numpy==1.21.6
      - optuna==4.0.0
      - pandas==1.3.5
      - pillow==9.5.0
      - pycodestyle==2.9.1
      - pydocstyle==6.3.0
      - pyflakes==2.5.0
      - pyparsing==3.1.4
      - pytest-flake8==1.1.3
      - pytest-mypy==0.10.3
      - pytest-pydocstyle==2.3.2
      - python-dateutil==2.9.0.post0
      - pytz==2025.2
      - pyyaml==6.0.1
      - scikit-learn==1.0.2
      - scipy==1.7.3
      - six==1.17.0
      - snowballstemmer==2.2.0
      - sqlalchemy==2.0.40
      - threadpoolctl==3.1.0
      - tqdm==4.67.1
      - typed-ast==1.5.5
      - typing-extensions==4.7.1
prefix: /opt/conda/envs/OptGBM

tests/test_sklearn.py::mypy tests/test_sklearn.py::mypy-status
tests/test_sklearn.py::test_fit_with_params[None-False] tests/test_sklearn.py::test_fit_with_params[None-True] tests/test_sklearn.py::test_fit_with_params[log_likelihood-False] tests/test_sklearn.py::test_fit_with_params[log_likelihood-True] tests/test_sklearn.py::test_fit_with_fit_params[auc-None] tests/test_sklearn.py::test_fit_with_fit_params[auc-callbacks1] tests/test_sklearn.py::test_fit_with_fit_params[zero_one_loss-None] tests/test_sklearn.py::test_fit_with_fit_params[zero_one_loss-callbacks1] tests/test_sklearn.py::test_fit_twice_without_study[-1] tests/test_sklearn.py::test_fit_twice_without_study[1] tests/test_sklearn.py::test_fit_twice_with_study[None] tests/test_sklearn.py::test_fit_twice_with_study[sqlite:///:memory:] tests/test_sklearn.py::test_refit[None] tests/test_sklearn.py::test_refit[10] tests/test_sklearn.py::test_score[load_breast_cancer] tests/test_sklearn.py::test_score[load_digits] tests/test_sklearn.py::test_score[load_iris] tests/test_sklearn.py::test_score[load_wine] tests/test_sklearn.py::test_plot_importance[-1] tests/test_sklearn.py::test_plot_importance[1]
tests/test_sklearn.py::flake-8::FLAKE8 tests/test_sklearn.py::test_ogbm_classifier tests/test_sklearn.py::test_ogbm_regressor
optgbm/sklearn.py
optgbm/sklearn.py