private void InitializeTrainingState(int fieldCount, int featureCount, FieldAwareFactorizationMachinePredictor predictor, out float[] linearWeights, out AlignedArray latentWeightsAligned, out float[] linearAccumulatedSquaredGrads, out AlignedArray latentAccumulatedSquaredGradsAligned) { linearWeights = new float[featureCount]; latentWeightsAligned = new AlignedArray(featureCount * fieldCount * _latentDimAligned, 16); linearAccumulatedSquaredGrads = new float[featureCount]; latentAccumulatedSquaredGradsAligned = new AlignedArray(featureCount * fieldCount * _latentDimAligned, 16); if (predictor == null) { var rng = Host.Rand; for (int j = 0; j < featureCount; j++) { linearWeights[j] = 0; linearAccumulatedSquaredGrads[j] = 1; for (int f = 0; f < fieldCount; f++) { int vBias = j * fieldCount * _latentDimAligned + f * _latentDimAligned; for (int k = 0; k < _latentDimAligned; k++) { if (k < _latentDim) { latentWeightsAligned[vBias + k] = _radius * (float)rng.NextDouble(); } else { latentWeightsAligned[vBias + k] = 0; } latentAccumulatedSquaredGradsAligned[vBias + k] = 1; } } } } else { predictor.CopyLinearWeightsTo(linearWeights); predictor.CopyLatentWeightsTo(latentWeightsAligned); for (int j = 0; j < featureCount; j++) { linearAccumulatedSquaredGrads[j] = 1; for (int f = 0; f < fieldCount; f++) { int vBias = j * fieldCount * _latentDimAligned + f * _latentDimAligned; for (int k = 0; k < _latentDimAligned; k++) { latentAccumulatedSquaredGradsAligned[vBias + k] = 1; } } } } }
private static double CalculateAvgLoss(IChannel ch, RoleMappedData data, bool norm, float[] linearWeights, AlignedArray latentWeightsAligned, int latentDimAligned, AlignedArray latentSum, int[] featureFieldBuffer, int[] featureIndexBuffer, float[] featureValueBuffer, VBuffer <float> buffer, ref long badExampleCount) { var featureColumns = data.Schema.GetColumns(RoleMappedSchema.ColumnRole.Feature); Func <int, bool> pred = c => featureColumns.Select(ci => ci.Index).Contains(c) || c == data.Schema.Label.Value.Index || c == data.Schema.Weight?.Index; var getters = new ValueGetter <VBuffer <float> > [featureColumns.Count]; float label = 0; float weight = 1; double loss = 0; float modelResponse = 0; long exampleCount = 0; badExampleCount = 0; int count = 0; using (var cursor = data.Data.GetRowCursor(pred)) { var labelGetter = RowCursorUtils.GetLabelGetter(cursor, data.Schema.Label.Value.Index); var weightGetter = data.Schema.Weight?.Index is int weightIdx?cursor.GetGetter <float>(weightIdx) : null; for (int f = 0; f < featureColumns.Count; f++) { getters[f] = cursor.GetGetter <VBuffer <float> >(featureColumns[f].Index); } while (cursor.MoveNext()) { labelGetter(ref label); weightGetter?.Invoke(ref weight); float annihilation = label - label + weight - weight; if (!FloatUtils.IsFinite(annihilation)) { badExampleCount++; continue; } if (!FieldAwareFactorizationMachineUtils.LoadOneExampleIntoBuffer(getters, buffer, norm, ref count, featureFieldBuffer, featureIndexBuffer, featureValueBuffer)) { badExampleCount++; continue; } FieldAwareFactorizationMachineInterface.CalculateIntermediateVariables(featureColumns.Count, latentDimAligned, count, featureFieldBuffer, featureIndexBuffer, featureValueBuffer, linearWeights, latentWeightsAligned, latentSum, ref modelResponse); loss += weight * CalculateLoss(label, modelResponse); exampleCount++; } } return(loss / exampleCount); }
internal FieldAwareFactorizationMachinePredictor(IHostEnvironment env, bool norm, int fieldCount, int featureCount, int latentDim, float[] linearWeights, AlignedArray latentWeightsAligned) : base(env, LoaderSignature) { Host.Assert(fieldCount > 0); Host.Assert(featureCount > 0); Host.Assert(latentDim > 0); Host.Assert(Utils.Size(linearWeights) == featureCount); LatentDimAligned = FieldAwareFactorizationMachineUtils.GetAlignedVectorLength(latentDim); Host.Assert(latentWeightsAligned.Size == checked (featureCount * fieldCount * LatentDimAligned)); _norm = norm; FieldCount = fieldCount; FeatureCount = featureCount; LatentDim = latentDim; _linearWeights = linearWeights; _latentWeightsAligned = latentWeightsAligned; }
public Row GetRow(Row input, Func <int, bool> predicate, out Action action) { var latentSum = new AlignedArray(_pred.FieldCount * _pred.FieldCount * _pred.LatentDimAligned, 16); var featureBuffer = new VBuffer <float>(); var featureFieldBuffer = new int[_pred.FeatureCount]; var featureIndexBuffer = new int[_pred.FeatureCount]; var featureValueBuffer = new float[_pred.FeatureCount]; var inputGetters = new ValueGetter <VBuffer <float> > [_pred.FieldCount]; if (predicate(0) || predicate(1)) { for (int f = 0; f < _pred.FieldCount; f++) { inputGetters[f] = input.GetGetter <VBuffer <float> >(_inputColumnIndexes[f]); } } action = null; var getters = new Delegate[2]; if (predicate(0)) { ValueGetter <float> responseGetter = (ref float value) => { value = _pred.CalculateResponse(inputGetters, featureBuffer, featureFieldBuffer, featureIndexBuffer, featureValueBuffer, latentSum); }; getters[0] = responseGetter; } if (predicate(1)) { ValueGetter <float> probGetter = (ref float value) => { value = _pred.CalculateResponse(inputGetters, featureBuffer, featureFieldBuffer, featureIndexBuffer, featureValueBuffer, latentSum); value = MathUtils.SigmoidSlow(value); }; getters[1] = probGetter; } return(new SimpleRow(OutputSchema, input, getters)); }
public static void CalculateIntermediateVariables(int fieldCount, int latentDim, int count, int[] fieldIndices, int[] featureIndices, float[] featureValues, float[] linearWeights, AlignedArray latentWeights, AlignedArray latentSum, ref float response) { Contracts.AssertNonEmpty(fieldIndices); Contracts.AssertNonEmpty(featureValues); Contracts.AssertNonEmpty(featureIndices); Contracts.AssertNonEmpty(linearWeights); Contracts.Assert(Compat(latentWeights)); Contracts.Assert(Compat(latentSum)); unsafe { fixed(int *pf = &fieldIndices[0]) fixed(int *pi = &featureIndices[0]) fixed(float *px = &featureValues[0]) fixed(float *pw = &linearWeights[0]) fixed(float *pv = &latentWeights.Items[0]) fixed(float *pq = &latentSum.Items[0]) fixed(float *pr = &response) CalculateIntermediateVariablesNative(fieldCount, latentDim, count, pf, pi, px, pw, Ptr(latentWeights, pv), Ptr(latentSum, pq), pr); } }
/// <summary> /// Initialize model parameters with a trained model. /// </summary> /// <param name="env">The host environment</param> /// <param name="norm">True if user wants to normalize feature vector to unit length.</param> /// <param name="fieldCount">The number of fileds, which is the symbol `m` in the doc: https://github.com/wschin/fast-ffm/blob/master/fast-ffm.pdf </param> /// <param name="featureCount">The number of features, which is the symbol `n` in the doc: https://github.com/wschin/fast-ffm/blob/master/fast-ffm.pdf </param> /// <param name="latentDim">The latent dimensions, which is the length of `v_{j, f}` in the doc: https://github.com/wschin/fast-ffm/blob/master/fast-ffm.pdf </param> /// <param name="linearWeights">The linear coefficients of the features, which is the symbol `w` in the doc: https://github.com/wschin/fast-ffm/blob/master/fast-ffm.pdf </param> /// <param name="latentWeights">Latent representation of each feature. Note that one feature may have <see cref="FieldCount"/> latent vectors /// and each latent vector contains <see cref="LatentDim"/> values. In the f-th field, the j-th feature's latent vector, `v_{j, f}` in the doc /// https://github.com/wschin/fast-ffm/blob/master/fast-ffm.pdf, starts at latentWeights[j * fieldCount * latentDim + f * latentDim]. /// The k-th element in v_{j, f} is latentWeights[j * fieldCount * latentDim + f * latentDim + k]. The size of the array must be featureCount x fieldCount x latentDim.</param> public FieldAwareFactorizationMachineModelParameters(IHostEnvironment env, bool norm, int fieldCount, int featureCount, int latentDim, float[] linearWeights, float[] latentWeights) : base(env, LoaderSignature) { Host.Assert(fieldCount > 0); Host.Assert(featureCount > 0); Host.Assert(latentDim > 0); Host.Assert(Utils.Size(linearWeights) == featureCount); LatentDimAligned = FieldAwareFactorizationMachineUtils.GetAlignedVectorLength(latentDim); Host.Assert(Utils.Size(latentWeights) == checked (featureCount * fieldCount * LatentDimAligned)); _norm = norm; FieldCount = fieldCount; FeatureCount = featureCount; LatentDim = latentDim; _linearWeights = linearWeights; _latentWeightsAligned = new AlignedArray(FeatureCount * FieldCount * LatentDimAligned, 16); for (int j = 0; j < FeatureCount; j++) { for (int f = 0; f < FieldCount; f++) { int index = j * FieldCount * LatentDim + f * LatentDim; int indexAligned = j * FieldCount * LatentDimAligned + f * LatentDimAligned; for (int k = 0; k < LatentDimAligned; k++) { if (k < LatentDim) { _latentWeightsAligned[indexAligned + k] = latentWeights[index + k]; } else { _latentWeightsAligned[indexAligned + k] = 0; } } } } }
private void TrainCore(IChannel ch, IProgressChannel pch, RoleMappedData data, RoleMappedData validData, FieldAwareFactorizationMachinePredictor predictor) { Host.AssertValue(ch); Host.AssertValue(pch); data.CheckBinaryLabel(); var featureColumns = data.Schema.GetColumns(RoleMappedSchema.ColumnRole.Feature); int fieldCount = featureColumns.Count; int totalFeatureCount = 0; int[] fieldColumnIndexes = new int[fieldCount]; for (int f = 0; f < fieldCount; f++) { var col = featureColumns[f]; Host.Assert(col.Type.AsVector.VectorSize > 0); if (col == null) throw ch.ExceptParam(nameof(data), "Empty feature column not allowed"); Host.Assert(!data.Schema.Schema.IsHidden(col.Index)); if (!col.Type.IsKnownSizeVector || col.Type.ItemType != NumberType.Float) throw ch.ExceptParam(nameof(data), "Training feature column '{0}' must be a known-size vector of R4, but has type: {1}.", col.Name, col.Type); fieldColumnIndexes[f] = col.Index; totalFeatureCount += col.Type.AsVector.VectorSize; } ch.Check(checked(totalFeatureCount * fieldCount * _latentDimAligned) <= Utils.ArrayMaxSize, "Latent dimension or the number of fields too large"); if (predictor != null) { ch.Check(predictor.FeatureCount == totalFeatureCount, "Input model's feature count mismatches training feature count"); ch.Check(predictor.LatentDim == _latentDim, "Input model's latent dimension mismatches trainer's"); } if (validData != null) { validData.CheckBinaryLabel(); var validFeatureColumns = data.Schema.GetColumns(RoleMappedSchema.ColumnRole.Feature); Host.Assert(fieldCount == validFeatureColumns.Count); for (int f = 0; f < fieldCount; f++) Host.Assert(featureColumns[f] == validFeatureColumns[f]); } bool shuffle = _shuffle; if (shuffle && !data.Data.CanShuffle) { ch.Warning("Training data does not support shuffling, so ignoring request to shuffle"); shuffle = false; } var rng = shuffle ? Host.Rand : null; var featureGetters = new ValueGetter<VBuffer<float>>[fieldCount]; var featureBuffer = new VBuffer<float>(); var featureValueBuffer = new float[totalFeatureCount]; var featureIndexBuffer = new int[totalFeatureCount]; var featureFieldBuffer = new int[totalFeatureCount]; var latentSum = new AlignedArray(fieldCount * fieldCount * _latentDimAligned, 16); var metricNames = new List<string>() { "Training-loss" }; if (validData != null) metricNames.Add("Validation-loss"); int iter = 0; long exampleCount = 0; long badExampleCount = 0; long validBadExampleCount = 0; double loss = 0; double validLoss = 0; pch.SetHeader(new ProgressHeader(metricNames.ToArray(), new string[] { "iterations", "examples" }), entry => { entry.SetProgress(0, iter, _numIterations); entry.SetProgress(1, exampleCount); }); Func<int, bool> pred = c => fieldColumnIndexes.Contains(c) || c == data.Schema.Label.Index || (data.Schema.Weight != null && c == data.Schema.Weight.Index); InitializeTrainingState(fieldCount, totalFeatureCount, predictor, out float[] linearWeights, out AlignedArray latentWeightsAligned, out float[] linearAccSqGrads, out AlignedArray latentAccSqGradsAligned); // refer to Algorithm 3 in https://github.com/wschin/fast-ffm/blob/master/fast-ffm.pdf while (iter++ < _numIterations) { using (var cursor = data.Data.GetRowCursor(pred, rng)) { var labelGetter = RowCursorUtils.GetLabelGetter(cursor, data.Schema.Label.Index); var weightGetter = data.Schema.Weight == null ? null : RowCursorUtils.GetGetterAs<float>(NumberType.R4, cursor, data.Schema.Weight.Index); for (int i = 0; i < fieldCount; i++) featureGetters[i] = cursor.GetGetter<VBuffer<float>>(fieldColumnIndexes[i]); loss = 0; exampleCount = 0; badExampleCount = 0; while (cursor.MoveNext()) { float label = 0; float weight = 1; int count = 0; float modelResponse = 0; labelGetter(ref label); weightGetter?.Invoke(ref weight); float annihilation = label - label + weight - weight; if (!FloatUtils.IsFinite(annihilation)) { badExampleCount++; continue; } if (!FieldAwareFactorizationMachineUtils.LoadOneExampleIntoBuffer(featureGetters, featureBuffer, _norm, ref count, featureFieldBuffer, featureIndexBuffer, featureValueBuffer)) { badExampleCount++; continue; } // refer to Algorithm 1 in [3] https://github.com/wschin/fast-ffm/blob/master/fast-ffm.pdf FieldAwareFactorizationMachineInterface.CalculateIntermediateVariables(fieldCount, _latentDimAligned, count, featureFieldBuffer, featureIndexBuffer, featureValueBuffer, linearWeights, latentWeightsAligned, latentSum, ref modelResponse); var slope = CalculateLossSlope(label, modelResponse); // refer to Algorithm 2 in [3] https://github.com/wschin/fast-ffm/blob/master/fast-ffm.pdf FieldAwareFactorizationMachineInterface.CalculateGradientAndUpdate(_lambdaLinear, _lambdaLatent, _learningRate, fieldCount, _latentDimAligned, weight, count, featureFieldBuffer, featureIndexBuffer, featureValueBuffer, latentSum, slope, linearWeights, latentWeightsAligned, linearAccSqGrads, latentAccSqGradsAligned); loss += weight * CalculateLoss(label, modelResponse); exampleCount++; } loss /= exampleCount; } if (_verbose) { if (validData == null) pch.Checkpoint(loss, iter, exampleCount); else { validLoss = CalculateAvgLoss(ch, validData, _norm, linearWeights, latentWeightsAligned, _latentDimAligned, latentSum, featureFieldBuffer, featureIndexBuffer, featureValueBuffer, featureBuffer, ref validBadExampleCount); pch.Checkpoint(loss, validLoss, iter, exampleCount); } } } if (badExampleCount != 0) ch.Warning($"Skipped {badExampleCount} examples with bad label/weight/features in training set"); if (validBadExampleCount != 0) ch.Warning($"Skipped {validBadExampleCount} examples with bad label/weight/features in validation set"); _pred = new FieldAwareFactorizationMachinePredictor(Host, _norm, fieldCount, totalFeatureCount, _latentDim, linearWeights, latentWeightsAligned); }
internal void CopyLatentWeightsTo(AlignedArray latentWeights) { Host.AssertValue(_latentWeightsAligned); Host.AssertValue(latentWeights); latentWeights.CopyFrom(_latentWeightsAligned); }
internal float CalculateResponse(ValueGetter <VBuffer <float> >[] getters, VBuffer <float> featureBuffer, int[] featureFieldBuffer, int[] featureIndexBuffer, float[] featureValueBuffer, AlignedArray latentSum) { int count = 0; float modelResponse = 0; FieldAwareFactorizationMachineUtils.LoadOneExampleIntoBuffer(getters, featureBuffer, _norm, ref count, featureFieldBuffer, featureIndexBuffer, featureValueBuffer); FieldAwareFactorizationMachineInterface.CalculateIntermediateVariables(FieldCount, LatentDimAligned, count, featureFieldBuffer, featureIndexBuffer, featureValueBuffer, _linearWeights, _latentWeightsAligned, latentSum, ref modelResponse); return(modelResponse); }
public static void CalculateGradientAndUpdate(float lambdaLinear, float lambdaLatent, float learningRate, int fieldCount, int latentDim, float weight, int count, int[] fieldIndices, int[] featureIndices, float[] featureValues, AlignedArray latentSum, float slope, float[] linearWeights, AlignedArray latentWeights, float[] linearAccumulatedSquaredGrads, AlignedArray latentAccumulatedSquaredGrads) { Contracts.AssertNonEmpty(fieldIndices); Contracts.AssertNonEmpty(featureIndices); Contracts.AssertNonEmpty(featureValues); Contracts.Assert(Compat(latentSum)); Contracts.AssertNonEmpty(linearWeights); Contracts.Assert(Compat(latentWeights)); Contracts.AssertNonEmpty(linearAccumulatedSquaredGrads); Contracts.Assert(Compat(latentAccumulatedSquaredGrads)); unsafe { fixed(int *pf = &fieldIndices[0]) fixed(int *pi = &featureIndices[0]) fixed(float *px = &featureValues[0]) fixed(float *pq = &latentSum.Items[0]) fixed(float *pw = &linearWeights[0]) fixed(float *pv = &latentWeights.Items[0]) fixed(float *phw = &linearAccumulatedSquaredGrads[0]) fixed(float *phv = &latentAccumulatedSquaredGrads.Items[0]) CalculateGradientAndUpdateNative(lambdaLinear, lambdaLatent, learningRate, fieldCount, latentDim, weight, count, pf, pi, px, Ptr(latentSum, pq), slope, pw, Ptr(latentWeights, pv), phw, Ptr(latentAccumulatedSquaredGrads, phv)); } }
private static bool Compat(AlignedArray a) { Contracts.AssertValue(a); Contracts.Assert(a.Size > 0); return(a.CbAlign == CbAlign); }