12 #ifndef MLPACK_METHODS_HOEFFDING_TREES_HOEFFDING_TREE_IMPL_HPP 13 #define MLPACK_METHODS_HOEFFDING_TREES_HOEFFDING_TREE_IMPL_HPP 22 template<
typename FitnessFunction,
23 template<
typename>
class NumericSplitType,
24 template<
typename>
class CategoricalSplitType>
25 template<
typename MatType>
32 const arma::Row<size_t>& labels,
33 const size_t numClasses,
34 const bool batchTraining,
35 const double successProbability,
36 const size_t maxSamples,
37 const size_t checkInterval,
38 const size_t minSamples,
39 const CategoricalSplitType<FitnessFunction>&
41 const NumericSplitType<FitnessFunction>& numericSplitIn) :
42 dimensionMappings(NULL),
45 numClasses(numClasses),
46 maxSamples((maxSamples == 0) ? size_t(-1) : maxSamples),
47 checkInterval(checkInterval),
48 minSamples(minSamples),
49 datasetInfo(new data::DatasetInfo(datasetInfoIn)),
51 successProbability(successProbability),
52 splitDimension(size_t(-1)),
54 majorityProbability(0.0),
59 ResetTree(categoricalSplitIn, numericSplitIn);
62 Train(data, labels, batchTraining);
65 template<
typename FitnessFunction,
66 template<
typename>
class NumericSplitType,
67 template<
typename>
class CategoricalSplitType>
73 const size_t numClasses,
74 const double successProbability,
75 const size_t maxSamples,
76 const size_t checkInterval,
77 const size_t minSamples,
78 const CategoricalSplitType<FitnessFunction>&
80 const NumericSplitType<FitnessFunction>& numericSplitIn,
81 std::unordered_map<
size_t, std::pair<size_t, size_t>>*
83 const bool copyDatasetInfo) :
84 dimensionMappings((dimensionMappingsIn != NULL) ? dimensionMappingsIn :
85 new
std::unordered_map<size_t,
std::pair<size_t, size_t>>()),
86 ownsMappings(dimensionMappingsIn == NULL),
88 numClasses(numClasses),
89 maxSamples((maxSamples == 0) ? size_t(-1) : maxSamples),
90 checkInterval(checkInterval),
91 minSamples(minSamples),
92 datasetInfo(copyDatasetInfo ? new
data::DatasetInfo(datasetInfo) :
94 ownsInfo(copyDatasetInfo),
95 successProbability(successProbability),
96 splitDimension(size_t(-1)),
98 majorityProbability(0.0),
105 ResetTree(categoricalSplitIn, numericSplitIn);
111 if (datasetInfo.
Type(i) == data::Datatype::categorical)
113 categoricalSplits.push_back(CategoricalSplitType<FitnessFunction>(
114 datasetInfo.
NumMappings(i), numClasses, categoricalSplitIn));
118 numericSplits.push_back(NumericSplitType<FitnessFunction>(numClasses,
125 template<
typename FitnessFunction,
126 template<
typename>
class NumericSplitType,
127 template<
typename>
class CategoricalSplitType>
134 new
std::unordered_map<size_t,
std::pair<size_t, size_t>>()),
138 maxSamples(size_t(-1)),
141 datasetInfo(new
data::DatasetInfo()),
143 successProbability(0.95),
144 splitDimension(size_t(-1)),
146 majorityProbability(0.0),
154 template<
typename FitnessFunction,
155 template<
typename>
class NumericSplitType,
156 template<
typename>
class CategoricalSplitType>
159 numericSplits(other.numericSplits),
160 categoricalSplits(other.categoricalSplits),
161 dimensionMappings(new
std::unordered_map<size_t,
162 std::pair<size_t, size_t>>(*other.dimensionMappings)),
164 numSamples(other.numSamples),
165 numClasses(other.numClasses),
166 maxSamples(other.maxSamples),
167 checkInterval(other.checkInterval),
168 minSamples(other.minSamples),
169 datasetInfo(new
data::DatasetInfo(*other.datasetInfo)),
171 successProbability(other.successProbability),
172 splitDimension(other.splitDimension),
173 majorityClass(other.majorityClass),
174 majorityProbability(other.majorityProbability),
175 categoricalSplit(other.categoricalSplit),
176 numericSplit(other.numericSplit)
179 for (
size_t i = 0; i < other.children.size(); ++i)
184 delete children[i]->datasetInfo;
185 children[i]->datasetInfo = this->datasetInfo;
186 children[i]->ownsInfo =
false;
188 delete children[i]->dimensionMappings;
189 children[i]->dimensionMappings = this->dimensionMappings;
190 children[i]->ownsMappings =
false;
195 template<
typename FitnessFunction,
196 template<
typename>
class NumericSplitType,
197 template<
typename>
class CategoricalSplitType>
200 numericSplits(
std::move(other.numericSplits)),
201 categoricalSplits(
std::move(other.categoricalSplits)),
202 dimensionMappings(other.dimensionMappings),
204 numSamples(other.numSamples),
205 numClasses(other.numClasses),
206 maxSamples(other.maxSamples),
207 checkInterval(other.checkInterval),
208 minSamples(other.minSamples),
209 datasetInfo(other.datasetInfo),
211 successProbability(other.successProbability),
212 splitDimension(other.splitDimension),
213 majorityClass(other.majorityClass),
214 majorityProbability(other.majorityProbability),
215 categoricalSplit(
std::move(other.categoricalSplit)),
216 numericSplit(
std::move(other.numericSplit))
219 other.dimensionMappings =
nullptr;
220 other.datasetInfo =
nullptr;
223 other.numSamples = 0;
224 other.numClasses = 0;
225 other.checkInterval = 0;
226 other.minSamples = 0;
227 other.successProbability = 0.0;
228 other.splitDimension = 0;
229 other.majorityClass = 0;
230 other.majorityProbability = 0.0;
234 template<
typename FitnessFunction,
235 template<
typename>
class NumericSplitType,
236 template<
typename>
class CategoricalSplitType>
243 numericSplits = other.numericSplits;
244 categoricalSplits = other.categoricalSplits;
245 dimensionMappings =
new std::unordered_map<size_t,
246 std::pair<size_t, size_t>>(*other.dimensionMappings);
248 numSamples = other.numSamples;
249 numClasses = other.numClasses;
250 maxSamples = other.maxSamples;
251 checkInterval = other.checkInterval;
252 minSamples = other.minSamples;
255 successProbability = other.successProbability;
256 splitDimension = other.splitDimension;
257 majorityClass = other.majorityClass;
258 majorityProbability = other.majorityProbability;
259 categoricalSplit = other.categoricalSplit;
260 numericSplit = other.numericSplit;
263 for (
size_t i = 0; i < other.children.size(); ++i)
268 delete children[i]->datasetInfo;
269 children[i]->datasetInfo = this->datasetInfo;
270 children[i]->ownsInfo =
false;
272 delete children[i]->dimensionMappings;
273 children[i]->dimensionMappings = this->dimensionMappings;
274 children[i]->ownsMappings =
false;
281 template<
typename FitnessFunction,
282 template<
typename>
class NumericSplitType,
283 template<
typename>
class CategoricalSplitType>
290 numericSplits = std::move(other.numericSplits);
291 categoricalSplits = std::move(other.categoricalSplits);
292 dimensionMappings = other.dimensionMappings;
294 numSamples = other.numSamples;
295 numClasses = other.numClasses;
296 maxSamples = other.maxSamples;
297 checkInterval = other.checkInterval;
298 minSamples = other.minSamples;
299 datasetInfo = other.datasetInfo;
301 successProbability = other.successProbability;
302 splitDimension = other.splitDimension;
303 majorityClass = other.majorityClass;
304 majorityProbability = other.majorityProbability;
305 categoricalSplit = std::move(other.categoricalSplit);
306 numericSplit = std::move(other.numericSplit);
309 other.dimensionMappings =
nullptr;
310 other.datasetInfo =
nullptr;
313 other.numSamples = 0;
314 other.numClasses = 0;
315 other.checkInterval = 0;
316 other.minSamples = 0;
317 other.successProbability = 0.0;
318 other.splitDimension = 0;
319 other.majorityClass = 0;
320 other.majorityProbability = 0.0;
326 template<
typename FitnessFunction,
327 template<
typename>
class NumericSplitType,
328 template<
typename>
class CategoricalSplitType>
333 delete dimensionMappings;
336 for (
size_t i = 0; i < children.size(); ++i)
341 template<
typename FitnessFunction,
342 template<
typename>
class NumericSplitType,
343 template<
typename>
class CategoricalSplitType>
344 template<
typename MatType>
350 const arma::Row<size_t>& labels,
351 const bool batchTraining,
352 const bool resetTree,
353 const size_t numClassesIn)
367 numClasses = (numClassesIn != 0) ? numClassesIn : arma::max(labels) + 1;
372 TrainInternal(data, labels, batchTraining);
376 template<
typename FitnessFunction,
377 template<
typename>
class NumericSplitType,
378 template<
typename>
class CategoricalSplitType>
379 template<
typename MatType>
386 const arma::Row<size_t>& labels,
387 const bool batchTraining,
388 const size_t numClassesIn)
397 numClasses = (numClassesIn != 0) ? numClassesIn : arma::max(labels) + 1;
402 TrainInternal(data, labels, batchTraining);
406 template<
typename FitnessFunction,
407 template<
typename>
class NumericSplitType,
408 template<
typename>
class CategoricalSplitType>
409 template<
typename VecType>
414 >
::Train(
const VecType& point,
const size_t label)
416 if (splitDimension ==
size_t(-1))
419 size_t numericIndex = 0;
420 size_t categoricalIndex = 0;
421 for (
size_t i = 0; i < point.n_rows; ++i)
423 if (datasetInfo->
Type(i) == data::Datatype::categorical)
424 categoricalSplits[categoricalIndex++].
Train(point[i], label);
425 else if (datasetInfo->
Type(i) == data::Datatype::numeric)
426 numericSplits[numericIndex++].
Train(point[i], label);
430 if (categoricalSplits.size() > 0)
432 majorityClass = categoricalSplits[0].MajorityClass();
433 majorityProbability = categoricalSplits[0].MajorityProbability();
437 majorityClass = numericSplits[0].MajorityClass();
438 majorityProbability = numericSplits[0].MajorityProbability();
442 if (numSamples % checkInterval == 0)
458 children[direction]->Train(point, label);
462 template<
typename FitnessFunction,
463 template<
typename>
class NumericSplitType,
464 template<
typename>
class CategoricalSplitType>
472 if (splitDimension !=
size_t(-1))
476 if (numSamples <= minSamples)
484 const double epsilon = std::sqrt(rSquared *
485 std::log(1.0 / (1.0 - successProbability)) / (2 * numSamples));
488 double largest = -DBL_MAX;
489 size_t largestIndex = 0;
490 double secondLargest = -DBL_MAX;
491 for (
size_t i = 0; i < categoricalSplits.size() + numericSplits.size(); ++i)
493 size_t type = dimensionMappings->at(i).first;
494 size_t index = dimensionMappings->at(i).second;
498 double bestGain = 0.0;
499 double secondBestGain = 0.0;
500 if (type == data::Datatype::categorical)
501 categoricalSplits[index].EvaluateFitnessFunction(bestGain,
503 else if (type == data::Datatype::numeric)
504 numericSplits[index].EvaluateFitnessFunction(bestGain, secondBestGain);
507 if (bestGain > largest)
509 secondLargest = largest;
513 else if (bestGain > secondLargest)
515 secondLargest = bestGain;
518 if (secondBestGain > secondLargest)
520 secondLargest = secondBestGain;
525 if ((largest > 0.0) &&
526 ((largest - secondLargest > epsilon) || (numSamples > maxSamples) ||
530 splitDimension = largestIndex;
531 const size_t type = dimensionMappings->at(largestIndex).first;
532 const size_t index = dimensionMappings->at(largestIndex).second;
533 if (type == data::Datatype::categorical)
536 majorityClass = categoricalSplits[index].MajorityClass();
537 return categoricalSplits[index].NumChildren();
541 majorityClass = numericSplits[index].MajorityClass();
542 return numericSplits[index].NumChildren();
552 typename FitnessFunction,
553 template<
typename>
class NumericSplitType,
554 template<
typename>
class CategoricalSplitType
562 this->successProbability = successProbability;
563 for (
size_t i = 0; i < children.size(); ++i)
568 typename FitnessFunction,
569 template<
typename>
class NumericSplitType,
570 template<
typename>
class CategoricalSplitType
578 this->minSamples = minSamples;
579 for (
size_t i = 0; i < children.size(); ++i)
584 typename FitnessFunction,
585 template<
typename>
class NumericSplitType,
586 template<
typename>
class CategoricalSplitType
594 this->maxSamples = maxSamples;
595 for (
size_t i = 0; i < children.size(); ++i)
600 typename FitnessFunction,
601 template<
typename>
class NumericSplitType,
602 template<
typename>
class CategoricalSplitType
610 this->checkInterval = checkInterval;
611 for (
size_t i = 0; i < children.size(); ++i)
616 typename FitnessFunction,
617 template<
typename>
class NumericSplitType,
618 template<
typename>
class CategoricalSplitType
620 template<
typename VecType>
628 if (datasetInfo->
Type(splitDimension) == data::Datatype::numeric)
629 return numericSplit.CalculateDirection(point[splitDimension]);
630 else if (datasetInfo->
Type(splitDimension) == data::Datatype::categorical)
631 return categoricalSplit.CalculateDirection(point[splitDimension]);
636 template<
typename FitnessFunction,
637 template<
typename>
class NumericSplitType,
638 template<
typename>
class CategoricalSplitType>
646 std::stack<const HoeffdingTree*> stack;
648 while (!stack.empty())
654 stack.push(&node->
Child(i));
660 typename FitnessFunction,
661 template<
typename>
class NumericSplitType,
662 template<
typename>
class CategoricalSplitType
664 template<
typename VecType>
671 if (children.size() == 0)
675 return majorityClass;
685 typename FitnessFunction,
686 template<
typename>
class NumericSplitType,
687 template<
typename>
class CategoricalSplitType
689 template<
typename VecType>
696 double& probability)
const 698 if (children.size() == 0)
701 prediction = majorityClass;
702 probability = majorityProbability;
714 typename FitnessFunction,
715 template<
typename>
class NumericSplitType,
716 template<
typename>
class CategoricalSplitType
718 template<
typename MatType>
725 predictions.set_size(data.n_cols);
726 for (
size_t i = 0; i < data.n_cols; ++i)
727 predictions[i] =
Classify(data.col(i));
732 typename FitnessFunction,
733 template<
typename>
class NumericSplitType,
734 template<
typename>
class CategoricalSplitType
736 template<
typename MatType>
742 arma::Row<size_t>& predictions,
743 arma::rowvec& probabilities)
const 745 predictions.set_size(data.n_cols);
746 probabilities.set_size(data.n_cols);
747 for (
size_t i = 0; i < data.n_cols; ++i)
748 Classify(data.col(i), predictions[i], probabilities[i]);
752 typename FitnessFunction,
753 template<
typename>
class NumericSplitType,
754 template<
typename>
class CategoricalSplitType
763 arma::Col<size_t> childMajorities;
764 if (dimensionMappings->at(splitDimension).first ==
765 data::Datatype::categorical)
767 categoricalSplits[dimensionMappings->at(splitDimension).second].Split(
768 childMajorities, categoricalSplit);
770 else if (dimensionMappings->at(splitDimension).first ==
771 data::Datatype::numeric)
773 numericSplits[dimensionMappings->at(splitDimension).second].Split(
774 childMajorities, numericSplit);
778 for (
size_t i = 0; i < childMajorities.n_elem; ++i)
784 if (categoricalSplits.size() == 0)
787 children.push_back(
new HoeffdingTree(*datasetInfo, numClasses,
788 successProbability, maxSamples, checkInterval, minSamples,
789 CategoricalSplitType<FitnessFunction>(0, numClasses),
790 numericSplits[0], dimensionMappings,
false));
792 else if (numericSplits.size() == 0)
795 children.push_back(
new HoeffdingTree(*datasetInfo, numClasses,
796 successProbability, maxSamples, checkInterval, minSamples,
797 categoricalSplits[0], NumericSplitType<FitnessFunction>(numClasses),
798 dimensionMappings,
false));
803 children.push_back(
new HoeffdingTree(*datasetInfo, numClasses,
804 successProbability, maxSamples, checkInterval, minSamples,
805 categoricalSplits[0], numericSplits[0], dimensionMappings,
false));
808 children[i]->MajorityClass() = childMajorities[i];
812 numericSplits.clear();
813 categoricalSplits.clear();
817 typename FitnessFunction,
818 template<
typename>
class NumericSplitType,
819 template<
typename>
class CategoricalSplitType
821 template<
typename Archive>
828 ar(CEREAL_NVP(splitDimension));
831 if (cereal::is_loading<Archive>() && ownsMappings && dimensionMappings)
832 delete dimensionMappings;
838 if (cereal::is_saving<Archive>())
842 if (cereal::is_loading<Archive>())
844 if (datasetInfo && ownsInfo)
852 for (
size_t i = 0; i < children.size(); ++i)
857 ar(CEREAL_NVP(majorityClass));
858 ar(CEREAL_NVP(majorityProbability));
862 if (splitDimension ==
size_t(-1))
865 ar(CEREAL_NVP(numSamples));
866 ar(CEREAL_NVP(numClasses));
867 ar(CEREAL_NVP(maxSamples));
868 ar(CEREAL_NVP(successProbability));
872 if (cereal::is_loading<Archive>())
875 numericSplits.clear();
876 categoricalSplits.clear();
879 if (datasetInfo->
Type(i) == data::Datatype::categorical)
880 categoricalSplits.push_back(CategoricalSplitType<FitnessFunction>(
883 numericSplits.push_back(
884 NumericSplitType<FitnessFunction>(numClasses));
888 categoricalSplit =
typename CategoricalSplitType<FitnessFunction>::
889 SplitInfo(numClasses);
890 numericSplit =
typename NumericSplitType<FitnessFunction>::SplitInfo();
899 ar(CEREAL_NVP(numericSplits));
902 ar(CEREAL_NVP(categoricalSplits));
907 if (datasetInfo->
Type(splitDimension) == data::Datatype::categorical)
908 ar(CEREAL_NVP(categoricalSplit));
910 ar(CEREAL_NVP(numericSplit));
915 if (cereal::is_loading<Archive>())
917 for (
size_t i = 0; i < children.size(); ++i)
921 if (children[i]->datasetInfo == datasetInfo)
922 children[i]->ownsInfo =
false;
923 children[i]->ownsMappings =
false;
926 numericSplits.clear();
927 categoricalSplits.clear();
932 successProbability = 0.0;
938 typename FitnessFunction,
939 template<
typename>
class NumericSplitType,
940 template<
typename>
class CategoricalSplitType
942 template<
typename MatType>
947 >::TrainInternal(
const MatType&
data,
948 const arma::Row<size_t>& labels,
949 const bool batchTraining)
954 checkInterval = data.n_cols;
956 size_t oldMaxSamples = maxSamples;
957 maxSamples = std::max(
size_t(data.n_cols - 1),
size_t(5));
958 for (
size_t i = 0; i < data.n_cols; ++i)
959 Train(data.col(i), labels[i]);
960 maxSamples = oldMaxSamples;
964 if (children.size() > 0)
971 std::vector<arma::uvec> indices(children.size(), arma::uvec(data.n_cols));
972 arma::Col<size_t> counts =
973 arma::zeros<arma::Col<size_t>>(children.size());
975 for (
size_t i = 0; i < data.n_cols; ++i)
978 size_t currentIndex = counts[direction];
979 indices[direction][currentIndex] = i;
985 for (
size_t i = 0; i < children.size(); ++i)
995 arma::Row<size_t> childLabels = labels.cols(
996 indices[i].subvec(0, counts[i] - 1));
1003 MatType childData = data.cols(indices[i].subvec(0, counts[i] - 1));
1004 children[i]->Train(childData, childLabels,
true);
1011 for (
size_t i = 0; i < data.n_cols; ++i)
1012 Train(data.col(i), labels[i]);
1017 typename FitnessFunction,
1018 template<
typename>
class NumericSplitType,
1019 template<
typename>
class CategoricalSplitType
1024 CategoricalSplitType
1025 >::ResetTree(
const CategoricalSplitType<FitnessFunction>& categoricalSplitIn,
1026 const NumericSplitType<FitnessFunction>& numericSplitIn)
1030 delete dimensionMappings;
1032 categoricalSplits.clear();
1033 numericSplits.clear();
1036 new std::unordered_map<size_t, std::pair<size_t, size_t>>();
1037 ownsMappings =
true;
1040 if (datasetInfo->
Type(i) == data::Datatype::categorical)
1042 categoricalSplits.push_back(CategoricalSplitType<FitnessFunction>(
1043 datasetInfo->
NumMappings(i), numClasses, categoricalSplitIn));
1044 (*dimensionMappings)[i] = std::make_pair(data::Datatype::categorical,
1045 categoricalSplits.size() - 1);
1049 numericSplits.push_back(NumericSplitType<FitnessFunction>(numClasses,
1051 (*dimensionMappings)[i] = std::make_pair(data::Datatype::numeric,
1052 numericSplits.size() - 1);
1057 for (
size_t i = 0; i < children.size(); ++i)
1063 splitDimension = size_t(-1);
1065 majorityProbability = 0.0;
1067 typename CategoricalSplitType<FitnessFunction>::SplitInfo(0);
1068 numericSplit =
typename NumericSplitType<FitnessFunction>::SplitInfo();
Auxiliary information for a dataset, including mappings to/from strings (or other types) and the data...
Definition: dataset_mapper.hpp:41
double SuccessProbability() const
Get the confidence required for a split.
Definition: hoeffding_tree.hpp:269
The HoeffdingTree object represents all of the necessary information for a Hoeffding-bound-based deci...
Definition: hoeffding_tree.hpp:61
Linear algebra utility functions, generally performed on matrices or vectors.
Definition: cv.hpp:1
void Train(const MatType &data, const arma::Row< size_t > &labels, const bool batchTraining=true, const bool resetTree=false, const size_t numClasses=0)
Train on a set of points, either in streaming mode or in batch mode, with the given labels...
Definition: hoeffding_tree_impl.hpp:349
~HoeffdingTree()
Clean up memory.
Definition: hoeffding_tree_impl.hpp:330
Definition: pointer_wrapper.hpp:23
size_t CalculateDirection(const VecType &point) const
Given a point and that this node is not a leaf, calculate the index of the child node this point woul...
Definition: hoeffding_tree_impl.hpp:625
RangeType< double > Range
3.0.0 TODO: break reverse-compatibility by changing RangeType to Range.
Definition: range.hpp:19
size_t MinSamples() const
Get the minimum number of samples for a split.
Definition: hoeffding_tree.hpp:274
size_t Dimensionality() const
Get the dimensionality of the DatasetMapper object (that is, how many dimensions it has information f...
Definition: dataset_mapper_impl.hpp:228
size_t Classify(const VecType &point) const
Classify the given point, using this node and the entire (sub)tree beneath it.
Definition: hoeffding_tree_impl.hpp:669
HoeffdingTree & operator=(const HoeffdingTree &other)
Copy assignment operator.
Definition: hoeffding_tree_impl.hpp:239
Datatype Type(const size_t dimension) const
Return the type of a given dimension (numeric or categorical).
Definition: dataset_mapper_impl.hpp:196
size_t NumDescendants() const
Get the size of the Hoeffding Tree.
Definition: hoeffding_tree_impl.hpp:643
#define CEREAL_VECTOR_POINTER(T)
Cereal does not support the serialization of raw pointer.
Definition: pointer_vector_wrapper.hpp:93
size_t CheckInterval() const
Get the number of samples before a split check is performed.
Definition: hoeffding_tree.hpp:284
HoeffdingTree()
Construct a Hoeffding tree with no data and no information.
Definition: hoeffding_tree_impl.hpp:132
size_t SplitCheck()
Check if a split would satisfy the conditions of the Hoeffding bound with the node's specified succes...
Definition: hoeffding_tree_impl.hpp:469
void serialize(Archive &ar, const uint32_t)
Serialize the split.
Definition: hoeffding_tree_impl.hpp:826
size_t NumMappings(const size_t dimension) const
Get the number of mappings for a particular dimension.
Definition: dataset_mapper_impl.hpp:222
#define CEREAL_POINTER(T)
Cereal does not support the serialization of raw pointer.
Definition: pointer_wrapper.hpp:96
size_t NumChildren() const
Get the number of children.
Definition: hoeffding_tree.hpp:261
size_t MaxSamples() const
Get the maximum number of samples before a split is forced.
Definition: hoeffding_tree.hpp:279
const HoeffdingTree & Child(const size_t i) const
Get a child.
Definition: hoeffding_tree.hpp:264
void CreateChildren()
Given that this node should split, create the children.
Definition: hoeffding_tree_impl.hpp:760