Current section
Files
Jump to
Current section
Files
lib/exgboost/dmatrix.ex
defmodule EXGBoost.DMatrix do
@moduledoc false
# Internal docs for development only
_docstring = """
Parameters
----------
data :
Data source of DMatrix.
label :
Label of the training data.
weight :
Weight for each instance.
.. note::
For ranking task, weights are per-group. In ranking task, one weight
is assigned to each group (not each data point). This is because we
only care about the relative ordering of data points within each group,
so it doesn't make sense to assign weights to individual data points.
base_margin :
Base margin used for boosting from existing model.
missing :
Value in the input data which needs to be present as a missing value. If
None, defaults to np.nan.
silent :
Whether print messages during construction
feature_names :
Set names for features.
feature_types :
Set types for features. When `enable_categorical` is set to `True`, string
"c" represents categorical data type while "q" represents numerical feature
type. For categorical features, the input is assumed to be preprocessed and
encoded by the users. The encoding can be done via
:py:class:`sklearn.preprocessing.OrdinalEncoder` or pandas dataframe
`.cat.codes` method. This is useful when users want to specify categorical
features without having to construct a dataframe as input.
nthread :
Number of threads to use for loading data when parallelization is
applicable. If -1, uses maximum threads available on the system.
group :
Group size for all ranking group.
qid :
Query ID for data samples, used for ranking.
label_lower_bound :
Lower bound for survival training.
label_upper_bound :
Upper bound for survival training.
feature_weights :
Set feature weights for column sampling.
enable_categorical :
.. versionadded:: 1.3.0
.. note:: This parameter is experimental
Experimental support of specializing for categorical features. Do not set
to True unless you are interested in development. Also, JSON/UBJSON
serialization format is required.
"""
alias EXGBoost.ArrayInterface
alias EXGBoost.Internal
@enforce_keys [
:ref,
:format
]
defstruct [
:ref,
:format
]
@type t :: %__MODULE__{
ref: reference(),
format: atom()
}
def get_float_info(dmatrix, feature)
when feature in [
"label",
"weight",
"base_margin",
"label_lower_bound",
"label_upper_bound",
"feature_weights"
],
do: EXGBoost.NIF.dmatrix_get_float_info(dmatrix.ref, feature) |> Internal.unwrap!()
def get_group(dmatrix), do: get_uint_info(dmatrix, "group")
def get_uint_info(dmatrix, "group"),
do: EXGBoost.NIF.dmatrix_get_uint_info(dmatrix.ref, "group_ptr") |> Internal.unwrap!()
def get_num_rows(dmatrix),
do: EXGBoost.NIF.dmatrix_num_row(dmatrix.ref) |> Internal.unwrap!()
def get_num_cols(dmatrix),
do: EXGBoost.NIF.dmatrix_num_col(dmatrix.ref) |> Internal.unwrap!()
def get_num_non_missing(dmatrix),
do: EXGBoost.NIF.dmatrix_num_non_missing(dmatrix.ref) |> Internal.unwrap!()
def get_data(dmatrix),
do:
EXGBoost.NIF.dmatrix_get_data_as_csr(dmatrix.ref, Jason.encode!(%{})) |> Internal.unwrap!()
def get_feature_names(dmatrix),
do:
EXGBoost.NIF.dmatrix_get_str_feature_info(dmatrix.ref, "feature_name") |> Internal.unwrap!()
def get_feature_types(dmatrix),
do:
EXGBoost.NIF.dmatrix_get_str_feature_info(dmatrix.ref, "feature_type") |> Internal.unwrap!()
def set_params(dmat, opts) do
options = Internal.dmatrix_str_feature_opts() ++ Internal.dmatrix_meta_feature_opts()
opts = Keyword.validate!(opts, options)
{meta_opts, opts} = Keyword.split(opts, Internal.dmatrix_meta_feature_opts())
{str_opts, _opts} = Keyword.split(opts, Internal.dmatrix_str_feature_opts())
args = Enum.into(Keyword.merge(meta_opts, str_opts), %{})
Enum.each(meta_opts, fn {key, value} ->
data_interface = ArrayInterface.from_tensor(value) |> Jason.encode!()
EXGBoost.NIF.dmatrix_set_info_from_interface(
dmat.ref,
Atom.to_string(key),
data_interface
)
end)
Enum.each(str_opts, fn {key, value} ->
EXGBoost.NIF.dmatrix_set_str_feature_info(dmat.ref, Atom.to_string(key), value)
end)
struct(dmat, args)
end
@doc """
Slice the DMatrix and return a new DMatrix that only contains rindex.
"""
def slice(dmat, %Nx.Tensor{shape: {_rows}} = r_index, opts \\ [])
when is_list(opts) do
opts = Keyword.validate!(opts, allow_groups: false)
allow_groups = Keyword.fetch!(opts, :allow_groups)
EXGBoost.NIF.dmatrix_slice(dmat.ref, Nx.to_binary(r_index), allow_groups)
end
@doc """
Export the quantile cuts used for training histogram-based models like `hist` and `approx`.
Useful for model compression.
Returns a tuple of {indptr, data} representing a CSC matrix of the cuts.
"""
def get_quantile_cut(%__MODULE__{} = dmat) do
# https://xgboost.readthedocs.io/en/stable/c.html#_CPPv423XGDMatrixGetQuantileCutK13DMatrixHandlePKcPPKcPPKc
# config – JSON configuration string. At the moment it should be an empty document, preserved for future use.
config = %{} |> Jason.encode!()
{indptr, data} =
EXGBoost.NIF.dmatrix_get_quantile_cut(dmat.ref, config)
|> Internal.unwrap!()
indptr =
Jason.decode!(indptr)
|> ArrayInterface.from_map()
|> ArrayInterface.get_tensor()
data =
Jason.decode!(data)
|> ArrayInterface.from_map()
|> ArrayInterface.get_tensor()
{indptr, data}
end
defimpl Inspect do
import Inspect.Algebra
alias EXGBoost.DMatrix
def inspect(dmatrix, _opts) do
{indptr, indices, data} = DMatrix.get_data(dmatrix)
concat([
"#DMatrix<",
line(),
" {#{DMatrix.get_num_rows(dmatrix)}x#{DMatrix.get_num_cols(dmatrix)}x#{DMatrix.get_num_non_missing(dmatrix)}}",
line(),
if(DMatrix.get_group(dmatrix) != nil,
do: " group: #{inspect(DMatrix.get_group(dmatrix))}"
),
line(),
" indptr: #{inspect(Nx.tensor(indptr))}",
line(),
" indices: #{inspect(Nx.tensor(indices))}",
line(),
" data: #{inspect(Nx.tensor(data))}",
line(),
">"
])
end
end
@doc """
Create a DMatrix from a file.
Refer to https://xgboost.readthedocs.io/en/latest/tutorials/external_memory.html#text-file-inputs
for proper formatting of the file and the options.
This function will URI encode the filepath according to the URI scheme defined in
XGBoost's documentation.
"""
def from_file(filepath, opts) when is_binary(filepath) and is_list(opts) do
opts =
Keyword.validate!(opts,
label_column: nil,
cacheprefix: nil,
format: :dense,
ext: :auto,
silent: 1,
data_split_mode: :row
)
if not (File.exists?(filepath) and File.regular?(filepath)) do
raise ArgumentError, "File must exist and be a regular file"
end
{file_format, opts} = Keyword.pop!(opts, :ext)
{silent, opts} = Keyword.pop!(opts, :silent)
{format, opts} = Keyword.pop!(opts, :format)
{label_column, opts} = Keyword.pop!(opts, :label_column)
{cacheprefix, opts} = Keyword.pop!(opts, :cacheprefix)
{data_split_mode, opts} = Keyword.pop!(opts, :data_split_mode)
unless data_split_mode in [:row, :column] do
raise ArgumentError, "data_split_mode must be :row or :column"
end
ext =
case file_format do
:libsvm -> "libsvm"
:csv -> "csv"
:auto -> "auto"
_ -> raise ArgumentError, "Invalid file format"
end
uri = "#{filepath}?format=#{ext}"
if file_format != :csv and not is_nil(label_column) do
if silent == 1 do
IO.warn("label_column should only be specified for CSV files -- ignoring...")
else
raise ArgumentError, "label_column should only be specified for CSV files"
end
end
if not is_nil(cacheprefix) and not File.exists?(cacheprefix) do
if silent == 1 do
IO.warn("cacheprefix file not found -- ignoring...")
else
raise ArgumentError, "cacheprefix file not found"
end
end
uri =
if not is_nil(label_column) and file_format == :csv do
uri <> "&label_column=#{label_column}"
else
uri
end
uri =
if not is_nil(cacheprefix) and File.exists?(cacheprefix) and File.regular?(cacheprefix) do
uri <> "##{cacheprefix}"
else
uri
end
config = %{uri: uri, silent: silent, data_split_mode: data_split_mode} |> Jason.encode!()
dmat =
EXGBoost.NIF.dmatrix_create_from_uri(config)
|> Internal.unwrap!()
set_params(%__MODULE__{ref: dmat, format: format}, opts)
end
def from_tensor(_tensor, _opts \\ [])
def from_tensor(%Nx.Tensor{} = tensor, opts) when is_list(opts) do
opts = Keyword.validate!(opts, Internal.dmatrix_feature_opts())
{config_opts, opts} = Keyword.split(opts, Internal.dmatrix_config_feature_opts())
config_opts = Keyword.validate!(config_opts, missing: Nx.Constants.nan(), nthread: 0)
{format_opts, opts} = Keyword.split(opts, Internal.dmatrix_format_feature_opts())
config = Enum.into(config_opts, %{}, fn {key, value} -> {Atom.to_string(key), value} end)
format = Keyword.fetch!(format_opts, :format)
dmat =
EXGBoost.NIF.dmatrix_create_from_dense(
Jason.encode!(ArrayInterface.from_tensor(tensor)),
Jason.encode!(config)
)
|> Internal.unwrap!()
set_params(%__MODULE__{ref: dmat, format: format}, opts)
end
def from_tensor(%Nx.Tensor{} = x, %Nx.Tensor{} = y) do
from_tensor(x, y, [])
end
def from_tensor(%Nx.Tensor{shape: x_shape}, %Nx.Tensor{shape: {y_shape}}, _opts)
when is_tuple(x_shape) and elem(x_shape, 0) != elem(y_shape, 0) do
raise ArgumentError,
"x and y must have the same number of rows, got #{elem(x_shape, 0)} and #{elem(y_shape, 0)}"
end
def from_tensor(%Nx.Tensor{shape: x_shape} = x, %Nx.Tensor{shape: y_shape} = y, opts)
when is_tuple(x_shape) and elem(x_shape, 0) == elem(y_shape, 0) do
if Keyword.has_key?(opts, :label) do
raise ArgumentError, "label must not be specified as an opt if y is provided"
end
opts = Keyword.put_new(opts, :label, y)
from_tensor(x, opts)
end
def from_csr(
%Nx.Tensor{} = indptr,
%Nx.Tensor{} = indices,
%Nx.Tensor{} = data,
n,
opts \\ []
)
when is_integer(n) and n > 0 do
from_csr({indptr, indices, data, n}, opts)
end
def from_csr(
{%Nx.Tensor{} = indptr, %Nx.Tensor{} = indices, %Nx.Tensor{} = data, n},
opts \\ []
)
when is_integer(n) and n > 0 do
opts = Keyword.validate!(opts, Internal.dmatrix_feature_opts())
{config_opts, opts} = Keyword.split(opts, Internal.dmatrix_config_feature_opts())
config_opts = Keyword.validate!(config_opts, missing: Nx.Constants.nan(), nthread: 0)
{format_opts, opts} = Keyword.split(opts, Internal.dmatrix_format_feature_opts())
config = Enum.into(config_opts, %{}, fn {key, value} -> {Atom.to_string(key), value} end)
format = Keyword.fetch!(format_opts, :format)
if format not in [:csr, :csc] do
raise ArgumentError, "Sparse format must be :csr or :csc"
end
dmat =
EXGBoost.NIF.dmatrix_create_from_sparse(
Jason.encode!(ArrayInterface.from_tensor(indptr)),
Jason.encode!(ArrayInterface.from_tensor(indices)),
Jason.encode!(ArrayInterface.from_tensor(data)),
n,
Jason.encode!(config),
Atom.to_string(format)
)
|> Internal.unwrap!()
set_params(%__MODULE__{ref: dmat, format: format}, opts)
end
end
defmodule EXGBoost.ProxyDMatrix do
@moduledoc false
@enforce_keys [:ref]
defstruct [:ref]
def proxy_dmatrix() do
p_ref = EXGBoost.NIF.proxy_dmatrix_create()
%__MODULE__{ref: p_ref}
end
def set_params(%__MODULE__{} = dmat, opts) do
EXGBoost.DMatrix.set_params(dmat, opts)
end
end