1 /*!
2 * Copyright 2015-2018 by Contributors
3 * \file multi_class.cc
4 * \brief Definition of multi-class classification objectives.
5 * \author Tianqi Chen
6 */
7 #include <dmlc/omp.h>
8
9 #include <vector>
10 #include <algorithm>
11 #include <limits>
12 #include <utility>
13
14 #include "xgboost/parameter.h"
15 #include "xgboost/data.h"
16 #include "xgboost/logging.h"
17 #include "xgboost/objective.h"
18 #include "xgboost/json.h"
19
20 #include "../common/common.h"
21 #include "../common/math.h"
22 #include "../common/transform.h"
23
24 namespace xgboost {
25 namespace obj {
26
27 #if defined(XGBOOST_USE_CUDA)
28 DMLC_REGISTRY_FILE_TAG(multiclass_obj_gpu);
29 #endif // defined(XGBOOST_USE_CUDA)
30
31 struct SoftmaxMultiClassParam : public XGBoostParameter<SoftmaxMultiClassParam> {
32 int num_class;
33 // declare parameters
DMLC_DECLARE_PARAMETERxgboost::obj::SoftmaxMultiClassParam34 DMLC_DECLARE_PARAMETER(SoftmaxMultiClassParam) {
35 DMLC_DECLARE_FIELD(num_class).set_lower_bound(1)
36 .describe("Number of output class in the multi-class classification.");
37 }
38 };
39
40 class SoftmaxMultiClassObj : public ObjFunction {
41 public:
SoftmaxMultiClassObj(bool output_prob)42 explicit SoftmaxMultiClassObj(bool output_prob)
43 : output_prob_(output_prob) {}
44
Configure(Args const & args)45 void Configure(Args const& args) override {
46 param_.UpdateAllowUnknown(args);
47 }
GetGradient(const HostDeviceVector<bst_float> & preds,const MetaInfo & info,int iter,HostDeviceVector<GradientPair> * out_gpair)48 void GetGradient(const HostDeviceVector<bst_float>& preds,
49 const MetaInfo& info,
50 int iter,
51 HostDeviceVector<GradientPair>* out_gpair) override {
52 // Remove unused parameter compiler warning.
53 (void) iter;
54
55 if (info.labels_.Size() == 0) {
56 return;
57 }
58 CHECK(preds.Size() == (static_cast<size_t>(param_.num_class) * info.labels_.Size()))
59 << "SoftmaxMultiClassObj: label size and pred size does not match.\n"
60 << "label.Size() * num_class: "
61 << info.labels_.Size() * static_cast<size_t>(param_.num_class) << "\n"
62 << "num_class: " << param_.num_class << "\n"
63 << "preds.Size(): " << preds.Size();
64
65 const int nclass = param_.num_class;
66 const auto ndata = static_cast<int64_t>(preds.Size() / nclass);
67
68 auto device = tparam_->gpu_id;
69 out_gpair->SetDevice(device);
70 info.labels_.SetDevice(device);
71 info.weights_.SetDevice(device);
72 preds.SetDevice(device);
73
74 label_correct_.Resize(1);
75 label_correct_.SetDevice(device);
76
77 out_gpair->Resize(preds.Size());
78 label_correct_.Fill(1);
79
80 const bool is_null_weight = info.weights_.Size() == 0;
81 if (!is_null_weight) {
82 CHECK_EQ(info.weights_.Size(), ndata)
83 << "Number of weights should be equal to number of data points.";
84 }
85
86 common::Transform<>::Init(
87 [=] XGBOOST_DEVICE(size_t idx,
88 common::Span<GradientPair> gpair,
89 common::Span<bst_float const> labels,
90 common::Span<bst_float const> preds,
91 common::Span<bst_float const> weights,
92 common::Span<int> _label_correct) {
93 common::Span<bst_float const> point = preds.subspan(idx * nclass, nclass);
94
95 // Part of Softmax function
96 bst_float wmax = std::numeric_limits<bst_float>::min();
97 for (auto const i : point) { wmax = fmaxf(i, wmax); }
98 double wsum = 0.0f;
99 for (auto const i : point) { wsum += expf(i - wmax); }
100 auto label = labels[idx];
101 if (label < 0 || label >= nclass) {
102 _label_correct[0] = 0;
103 label = 0;
104 }
105 bst_float wt = is_null_weight ? 1.0f : weights[idx];
106 for (int k = 0; k < nclass; ++k) {
107 // Computation duplicated to avoid creating a cache.
108 bst_float p = expf(point[k] - wmax) / static_cast<float>(wsum);
109 const float eps = 1e-16f;
110 const bst_float h = fmax(2.0f * p * (1.0f - p) * wt, eps);
111 p = label == k ? p - 1.0f : p;
112 gpair[idx * nclass + k] = GradientPair(p * wt, h);
113 }
114 }, common::Range{0, ndata}, device, false)
115 .Eval(out_gpair, &info.labels_, &preds, &info.weights_, &label_correct_);
116
117 std::vector<int>& label_correct_h = label_correct_.HostVector();
118 for (auto const flag : label_correct_h) {
119 if (flag != 1) {
120 LOG(FATAL) << "SoftmaxMultiClassObj: label must be in [0, num_class).";
121 }
122 }
123 }
PredTransform(HostDeviceVector<bst_float> * io_preds) const124 void PredTransform(HostDeviceVector<bst_float>* io_preds) const override {
125 this->Transform(io_preds, output_prob_);
126 }
EvalTransform(HostDeviceVector<bst_float> * io_preds)127 void EvalTransform(HostDeviceVector<bst_float>* io_preds) override {
128 this->Transform(io_preds, true);
129 }
DefaultEvalMetric() const130 const char* DefaultEvalMetric() const override {
131 return "mlogloss";
132 }
133
Transform(HostDeviceVector<bst_float> * io_preds,bool prob) const134 inline void Transform(HostDeviceVector<bst_float> *io_preds, bool prob) const {
135 const int nclass = param_.num_class;
136 const auto ndata = static_cast<int64_t>(io_preds->Size() / nclass);
137
138 auto device = io_preds->DeviceIdx();
139 if (prob) {
140 common::Transform<>::Init(
141 [=] XGBOOST_DEVICE(size_t _idx, common::Span<bst_float> _preds) {
142 common::Span<bst_float> point =
143 _preds.subspan(_idx * nclass, nclass);
144 common::Softmax(point.begin(), point.end());
145 },
146 common::Range{0, ndata}, device)
147 .Eval(io_preds);
148 } else {
149 io_preds->SetDevice(device);
150 HostDeviceVector<bst_float> max_preds;
151 max_preds.SetDevice(device);
152 max_preds.Resize(ndata);
153 common::Transform<>::Init(
154 [=] XGBOOST_DEVICE(size_t _idx, common::Span<const bst_float> _preds,
155 common::Span<bst_float> _max_preds) {
156 common::Span<const bst_float> point =
157 _preds.subspan(_idx * nclass, nclass);
158 _max_preds[_idx] =
159 common::FindMaxIndex(point.cbegin(), point.cend()) -
160 point.cbegin();
161 },
162 common::Range{0, ndata}, device, false)
163 .Eval(io_preds, &max_preds);
164 io_preds->Resize(max_preds.Size());
165 io_preds->Copy(max_preds);
166 }
167 }
168
SaveConfig(Json * p_out) const169 void SaveConfig(Json* p_out) const override {
170 auto& out = *p_out;
171 if (this->output_prob_) {
172 out["name"] = String("multi:softprob");
173 } else {
174 out["name"] = String("multi:softmax");
175 }
176 out["softmax_multiclass_param"] = ToJson(param_);
177 }
178
LoadConfig(Json const & in)179 void LoadConfig(Json const& in) override {
180 FromJson(in["softmax_multiclass_param"], ¶m_);
181 }
182
183 private:
184 // output probability
185 bool output_prob_;
186 // parameter
187 SoftmaxMultiClassParam param_;
188 // Cache for max_preds
189 HostDeviceVector<int> label_correct_;
190 };
191
192 // register the objective functions
193 DMLC_REGISTER_PARAMETER(SoftmaxMultiClassParam);
194
195 XGBOOST_REGISTER_OBJECTIVE(SoftmaxMultiClass, "multi:softmax")
196 .describe("Softmax for multi-class classification, output class index.")
__anone499ac910102() 197 .set_body([]() { return new SoftmaxMultiClassObj(false); });
198
199 XGBOOST_REGISTER_OBJECTIVE(SoftprobMultiClass, "multi:softprob")
200 .describe("Softmax for multi-class classification, output probability distribution.")
__anone499ac910202() 201 .set_body([]() { return new SoftmaxMultiClassObj(true); });
202
203 } // namespace obj
204 } // namespace xgboost
205