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"], &param_);
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