loss_abstract.h 11.7 KB
Newer Older
Davis King's avatar
Davis King committed
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
// Copyright (C) 2015  Davis E. King (davis@dlib.net)
// License: Boost Software License   See LICENSE.txt for the full license.
#undef DLIB_DNn_LOSS_ABSTRACT_H_
#ifdef DLIB_DNn_LOSS_ABSTRACT_H_

#include "core_abstract.h"

namespace dlib
{

// ----------------------------------------------------------------------------------------

    class EXAMPLE_LOSS_LAYER_
    {
        /*!
            WHAT THIS OBJECT REPRESENTS
                A loss layer is the final layer in a deep neural network.  It computes the
                task loss.  That is, it computes a number that tells us how well the
                network is performing on some task, such as predicting a binary label.  

                You can use one of the loss layers that comes with dlib (defined below).
                But importantly, you are able to define your own loss layers to suit your
                needs.  You do this by creating a class that defines an interface matching
                the one described by this EXAMPLE_LOSS_LAYER_ class.  Note that there is no
                dlib::EXAMPLE_LOSS_LAYER_ type.  It is shown here purely to document the
Davis King's avatar
Davis King committed
26
                interface that a loss layer must implement.
Davis King's avatar
Davis King committed
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44

                A loss layer can optionally provide a to_label() method that converts the
                output of a network into a user defined type.  If to_label() is not
                provided then the operator() methods of add_loss_layer will not be
                available, but otherwise everything will function as normal.

                Finally, note that there are two broad flavors of loss layer, supervised
                and unsupervised.  The EXAMPLE_LOSS_LAYER_ as shown here is a supervised
                layer.  To make an unsupervised loss you simply leave out the label_type
                typedef, to_label(), and the truth iterator argument to compute_loss().
        !*/

    public:

        // sample_expansion_factor must be > 0
        const static unsigned int sample_expansion_factor;
        typedef whatever_type_you_use_for_labels label_type;

45
46
47
48
49
50
51
        EXAMPLE_LOSS_LAYER_ (
            const EXAMPLE_LOSS_LAYER_& item
        );
        /*!
            ensures
                - EXAMPLE_LOSS_LAYER_ objects are copy constructable
        !*/
Davis King's avatar
Davis King committed
52
53
54
55
56
57
58

        // Implementing to_label() is optional.
        template <
            typename SUB_TYPE,
            typename label_iterator
            >
        void to_label (
59
            const tensor& input_tensor,
Davis King's avatar
Davis King committed
60
61
62
63
64
            const SUB_TYPE& sub,
            label_iterator iter
        ) const;
        /*!
            requires
Davis King's avatar
Davis King committed
65
                - SUBNET implements the SUBNET interface defined at the top of
Davis King's avatar
Davis King committed
66
                  layers_abstract.h.
67
68
69
70
                - input_tensor was given as input to the network sub and the outputs are
                  now visible in layer<i>(sub).get_output(), for all valid i.
                - input_tensor.num_samples() > 0
                - input_tensor.num_samples()%sample_expansion_factor == 0.
Davis King's avatar
Davis King committed
71
                - iter == an iterator pointing to the beginning of a range of
72
73
                  input_tensor.num_samples()/sample_expansion_factor elements.  Moreover,
                  they must be label_type elements.
Davis King's avatar
Davis King committed
74
75
76
            ensures
                - Converts the output of the provided network to label_type objects and
                  stores the results into the range indicated by iter.  In particular, for
77
                  all valid i, it will be the case that:
Davis King's avatar
Davis King committed
78
79
                    *(iter+i/sample_expansion_factor) is populated based on the output of
                    sub and corresponds to the ith sample in input_tensor.
Davis King's avatar
Davis King committed
80
81
82
83
        !*/

        template <
            typename const_label_iterator,
Davis King's avatar
Davis King committed
84
            typename SUBNET
Davis King's avatar
Davis King committed
85
86
87
88
            >
        double compute_loss (
            const tensor& input_tensor,
            const_label_iterator truth, 
Davis King's avatar
Davis King committed
89
            SUBNET& sub
Davis King's avatar
Davis King committed
90
91
92
        ) const;
        /*!
            requires
Davis King's avatar
Davis King committed
93
                - SUBNET implements the SUBNET interface defined at the top of
Davis King's avatar
Davis King committed
94
95
96
97
98
99
                  layers_abstract.h.
                - input_tensor was given as input to the network sub and the outputs are
                  now visible in layer<i>(sub).get_output(), for all valid i.
                - input_tensor.num_samples() > 0
                - input_tensor.num_samples()%sample_expansion_factor == 0.
                - for all valid i:
Davis King's avatar
Davis King committed
100
101
                    - layer<i>(sub).get_gradient_input() has the same dimensions as
                      layer<i>(sub).get_output().
Davis King's avatar
Davis King committed
102
                - truth == an iterator pointing to the beginning of a range of
103
104
105
                  input_tensor.num_samples()/sample_expansion_factor elements.  Moreover,
                  they must be label_type elements.
                - for all valid i:
Davis King's avatar
Davis King committed
106
                    - *(truth+i/sample_expansion_factor) is the label of the ith sample in
107
                      input_tensor.
Davis King's avatar
Davis King committed
108
            ensures
Davis King's avatar
Davis King committed
109
                - This function computes a loss function that describes how well the output
Davis King's avatar
Davis King committed
110
111
112
113
114
115
116
117
118
119
120
                  of sub matches the expected labels given by truth.  Let's write the loss
                  function as L(input_tensor, truth, sub).  
                - Then compute_loss() computes the gradient of L() with respect to the
                  outputs in sub.  Specifically, compute_loss() adds the gradients into sub
                  by performing the following tensor additions, for all valid i: 
                    - layer<i>(sub).get_gradient_input() += the gradient of
                      L(input_tensor,truth,sub) with respect to layer<i>(sub).get_output().
                - returns L(input_tensor,truth,sub)
        !*/
    };

121
122
123
124
125
126
    void serialize(const EXAMPLE_LOSS_LAYER_& item, std::ostream& out);
    void deserialize(EXAMPLE_LOSS_LAYER_& item, std::istream& in);
    /*!
        provides serialization support  
    !*/

Davis King's avatar
Davis King committed
127
128
129
130
    // For each loss layer you define, always define an add_loss_layer template so that
    // layers can be easily composed.  Moreover, the convention is that the layer class
    // ends with an _ while the add_loss_layer template has the same name but without the
    // trailing _.
Davis King's avatar
Davis King committed
131
132
    template <typename SUBNET>
    using EXAMPLE_LOSS_LAYER = add_loss_layer<EXAMPLE_LOSS_LAYER_, SUBNET>;
Davis King's avatar
Davis King committed
133
134
135
136
137
138
139
140
141

// ----------------------------------------------------------------------------------------
// ----------------------------------------------------------------------------------------
// ----------------------------------------------------------------------------------------

    class loss_binary_hinge_ 
    {
        /*!
            WHAT THIS OBJECT REPRESENTS
Davis King's avatar
Davis King committed
142
                This object implements the loss layer interface defined above by
143
144
145
146
147
                EXAMPLE_LOSS_LAYER_.  In particular, it implements the hinge loss, which is
                appropriate for binary classification problems.  Therefore, the possible
                labels when using this loss are +1 and -1.  Moreover, it will cause the
                network to produce outputs > 0 when predicting a member of the +1 class and
                values < 0 otherwise.
Davis King's avatar
Davis King committed
148
149
150
151
152
153
154
155
156
157
158
        !*/
    public:

        const static unsigned int sample_expansion_factor = 1;
        typedef float label_type;

        template <
            typename SUB_TYPE,
            typename label_iterator
            >
        void to_label (
159
            const tensor& input_tensor,
Davis King's avatar
Davis King committed
160
161
162
163
164
165
166
167
168
            const SUB_TYPE& sub,
            label_iterator iter
        ) const;
        /*!
            This function has the same interface as EXAMPLE_LOSS_LAYER_::to_label() except
            it has the additional calling requirements that: 
                - sub.get_output().nr() == 1
                - sub.get_output().nc() == 1
                - sub.get_output().k() == 1
169
170
171
172
                - sub.get_output().num_samples() == input_tensor.num_samples()
            and the output label is the raw score for each classified object.  If the score
            is > 0 then the classifier is predicting the +1 class, otherwise it is
            predicting the -1 class.
Davis King's avatar
Davis King committed
173
174
175
176
        !*/

        template <
            typename const_label_iterator,
Davis King's avatar
Davis King committed
177
            typename SUBNET
Davis King's avatar
Davis King committed
178
179
180
181
            >
        double compute_loss (
            const tensor& input_tensor,
            const_label_iterator truth, 
Davis King's avatar
Davis King committed
182
            SUBNET& sub
Davis King's avatar
Davis King committed
183
184
185
186
187
188
189
        ) const;
        /*!
            This function has the same interface as EXAMPLE_LOSS_LAYER_::to_label() except
            it has the additional calling requirements that: 
                - sub.get_output().nr() == 1
                - sub.get_output().nc() == 1
                - sub.get_output().k() == 1
190
                - sub.get_output().num_samples() == input_tensor.num_samples()
Davis King's avatar
Davis King committed
191
192
193
194
195
                - all values pointed to by truth are +1 or -1.
        !*/

    };

196
197
198
199
200
201
    void serialize(const loss_binary_hinge_& item, std::ostream& out);
    void deserialize(loss_binary_hinge_& item, std::istream& in);
    /*!
        provides serialization support  
    !*/

Davis King's avatar
Davis King committed
202
203
    template <typename SUBNET>
    using loss_binary_hinge = add_loss_layer<loss_binary_hinge_, SUBNET>;
Davis King's avatar
Davis King committed
204

205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
// ----------------------------------------------------------------------------------------

    class loss_binary_log_ 
    {
        /*!
            WHAT THIS OBJECT REPRESENTS
                This object implements the loss layer interface defined above by
                EXAMPLE_LOSS_LAYER_.  In particular, it implements the log loss, which is
                appropriate for binary classification problems.  Therefore, the possible
                labels when using this loss are +1 and -1.  Moreover, it will cause the
                network to produce outputs > 0 when predicting a member of the +1 class and
                values < 0 otherwise.

                To be more specific, this object contains a sigmoid layer followed by a 
                cross-entropy layer.  
        !*/
    public:

        const static unsigned int sample_expansion_factor = 1;
        typedef float label_type;

        template <
            typename SUB_TYPE,
            typename label_iterator
            >
        void to_label (
            const tensor& input_tensor,
            const SUB_TYPE& sub,
            label_iterator iter
        ) const;
        /*!
            This function has the same interface as EXAMPLE_LOSS_LAYER_::to_label() except
            it has the additional calling requirements that: 
                - sub.get_output().nr() == 1
                - sub.get_output().nc() == 1
                - sub.get_output().k() == 1
                - sub.get_output().num_samples() == input_tensor.num_samples()
            and the output label is the raw score for each classified object.  If the score
            is > 0 then the classifier is predicting the +1 class, otherwise it is
            predicting the -1 class.
        !*/

        template <
            typename const_label_iterator,
            typename SUBNET
            >
        double compute_loss (
            const tensor& input_tensor,
            const_label_iterator truth, 
            SUBNET& sub
        ) const;
        /*!
            This function has the same interface as EXAMPLE_LOSS_LAYER_::to_label() except
            it has the additional calling requirements that: 
                - sub.get_output().nr() == 1
                - sub.get_output().nc() == 1
                - sub.get_output().k() == 1
                - sub.get_output().num_samples() == input_tensor.num_samples()
                - all values pointed to by truth are +1 or -1.
        !*/

    };

    void serialize(const loss_binary_log_& item, std::ostream& out);
    void deserialize(loss_binary_log_& item, std::istream& in);
    /*!
        provides serialization support  
    !*/

    template <typename SUBNET>
    using loss_binary_log = add_loss_layer<loss_binary_log_, SUBNET>;

Davis King's avatar
Davis King committed
277
278
279
280
281
282
// ----------------------------------------------------------------------------------------

}

#endif // DLIB_DNn_LOSS_ABSTRACT_H_