├── LICENSE ├── Makefile ├── README.md ├── bin └── .gitignore ├── fm_predict.cpp ├── fm_train.cpp └── src ├── FTRL ├── ftrl_model.h ├── ftrl_predictor.h └── ftrl_trainer.h ├── Frame ├── Makefile ├── pc_frame.cpp ├── pc_frame.h ├── pc_task.h ├── test_main.cpp └── test_task.h ├── Sample └── fm_sample.h └── Utils ├── utils.cpp └── utils.h /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Zhang Wei 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | all: 2 | g++ -O3 fm_train.cpp src/Frame/pc_frame.cpp src/Utils/utils.cpp -I . -std=c++0x -o bin/fm_train_softmax -lpthread 3 | g++ -O3 fm_predict.cpp src/Frame/pc_frame.cpp src/Utils/utils.cpp -I . -std=c++0x -o bin/fm_predict_softmax -lpthread 4 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # alphaFM_softmax 2 | ## 前言: 3 | * alphaFM_softmax是[alphaFM](https://github.com/CastellanZhang/alphaFM)的多分类版本。
4 | 5 | * 算法原理见我的博客文章:http://castellanzhang.github.io/2016/10/16/fm_ftrl_softmax/ 6 | 7 | * 当将dim参数设置为1,1,0时,alphaFM_softmax就退化成标准的softmax的FTRL训练工具。
8 | 9 | ## 安装方法: 10 | 直接在根目录make即可,编译后会在bin目录下生成两个可执行文件。如果编译失败,请升级gcc版本。 11 | ## 输入文件格式: 12 | 类似于libsvm格式,但更加灵活:特征编号不局限于整数也可以是字符串;特征值可以是整数或浮点数(特征值最好做归一化处理,否则可能会导致结果为nan), 13 | 特征值为0的项可以省略不写;label必须是1到k(假设类别数为k)。举例如下:
14 | `1 sex:1 age:0.3 f1:1 f3:0.9`
15 | `3 sex:0 age:0.7 f2:0.4 f5:0.8 f8:1`
16 | `2 sex:0 age:0.2 f2:0.6 f8:1`
17 | `...`
18 | ## 模型文件格式: 19 | 假定v的维度为f,类别数为k,类似于alphaFM的格式,只是feature_name之后的部分从1段变成了k段
20 | 第一行是bias的参数:
21 | `bias w_1 w_n_1 w_z_1 w_2 w_n_2 w_z_2 ... w_k w_n_k w_z_k`
22 | 其他行的格式为:
23 | `feature_name w_1 v1_1 v2_1 ... vf_1 w_n_1 w_z_1 v_n1_1 v_n2_1 ... v_nf_1 v_z1_1 v_z2_1 ... v_zf_1 w_2 v1_2 v2_2 ... vf_2 w_n_2 w_z_2 v_n1_2 v_n2_2 ... v_nf_2 v_z1_2 v_z2_2 ... v_zf_2 ... ... w_k v1_k v2_k ... vf_k w_n_k w_z_k v_n1_k v_n2_k ... v_nf_k v_z1_k v_z2_k ... v_zf_k` 24 | ## 预测结果格式: 25 | `label score_1 score_2 ... score_k`
26 | label为输入数据的类别标注值(1到k),score_i为样本属于类别i的预测概率值。 27 | 28 | ## 参数说明: 29 | ### fm_train_softmax的参数: 30 | 和alphaFM基本一致,多了一个-cn参数,少了一个-fvs参数
31 | -m \: 设置模型文件的输出路径。
32 | -cn \: 设置类别数。
33 | -dim \: k0为1表示使用偏置w0参数,0表示不使用;k1为1表示使用w参数,为0表示不使用;k2为v的维度,可以是0。 default:1,1,8
34 | -init_stdev \: v的初始化使用均值为0的高斯分布,stdev为标准差。 default:0.1
35 | -w_alpha \: w0和w的FTRL超参数alpha。 default:0.05
36 | -w_beta \: w0和w的FTRL超参数beta。 default:1.0
37 | -w_l1 \: w0和w的L1正则。 default:0.1
38 | -w_l2 \: w0和w的L2正则。 default:5.0
39 | -v_alpha \: v的FTRL超参数alpha。 default:0.05
40 | -v_beta \: v的FTRL超参数beta。 default:1.0
41 | -v_l1 \: v的L1正则。 default:0.1
42 | -v_l2 \: v的L2正则。 default:5.0
43 | -core \: 计算线程数。 default:1
44 | -im \: 上次模型的路径,用于初始化模型参数。如果是第一次训练则不用设置此参数。
45 | ### fm_predict_softmax的参数: 46 | 比alphaFM多了一个-cn参数
47 | -m \: 模型文件路径。
48 | -cn \: 设置类别数。
49 | -dim \: v的维度。 default:8
50 | -core \: 计算线程数。 default:1
51 | -out \: 输出文件路径。
52 | ## 计算速度: 53 | ### 我的实验结果: 54 | 本地1000万的样本,200万的特征维度,7个类别,v的维度为12,2.10GHz的CPU,开10个线程,非缺省参数如下:
55 | `-cn 7 -dim 1,1,12 -w_l1 0.05 -v_l1 0.05 -init_stdev 0.001 -w_alpha 0.01 -v_alpha 0.01 -core 10`
56 | 训练时间大概半个多小时。 57 | 58 | -------------------------------------------------------------------------------- /bin/.gitignore: -------------------------------------------------------------------------------- 1 | # Ignore everything in this directory 2 | * 3 | # Except this file !.gitignore 4 | !.gitignore 5 | -------------------------------------------------------------------------------- /fm_predict.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include "src/Frame/pc_frame.h" 5 | #include "src/FTRL/ftrl_predictor.h" 6 | 7 | using namespace std; 8 | 9 | struct Option 10 | { 11 | Option() : dim(8), threads_num(1), class_num(0) {} 12 | string model_path, predict_path; 13 | int threads_num, dim, class_num; 14 | }; 15 | 16 | string predict_help() 17 | { 18 | return string( 19 | "\nusage: cat sample | ./fm_predict_softmax []" 20 | "\n" 21 | "\n" 22 | "options:\n" 23 | "-m : set the model path\n" 24 | "-cn : set the number of classes\n" 25 | "-dim : dim of 2-way interactions\tdefault:8\n" 26 | "-core : set the number of threads\tdefault:1\n" 27 | "-out : set the predict path\n" 28 | ); 29 | } 30 | 31 | vector argv_to_args(int argc, char* argv[]) 32 | { 33 | vector args; 34 | for(int i = 1; i < argc; ++i) 35 | { 36 | args.push_back(string(argv[i])); 37 | } 38 | return args; 39 | } 40 | 41 | Option parse_option(const vector& args) 42 | { 43 | int argc = args.size(); 44 | if(0 == argc) 45 | throw invalid_argument("invalid command\n"); 46 | Option opt; 47 | 48 | for(int i = 0; i < argc; ++i) 49 | { 50 | if(args[i].compare("-m") == 0) 51 | { 52 | if(i == argc - 1) 53 | throw invalid_argument("invalid command\n"); 54 | opt.model_path = args[++i]; 55 | } 56 | else if(args[i].compare("-cn") == 0) 57 | { 58 | if(i == argc - 1) 59 | throw invalid_argument("invalid command\n"); 60 | opt.class_num = stoi(args[++i]); 61 | } 62 | else if(args[i].compare("-dim") == 0) 63 | { 64 | if(i == argc - 1) 65 | throw invalid_argument("invalid command\n"); 66 | opt.dim = stoi(args[++i]); 67 | } 68 | else if(args[i].compare("-core") == 0) 69 | { 70 | if(i == argc - 1) 71 | throw invalid_argument("invalid command\n"); 72 | opt.threads_num = stoi(args[++i]); 73 | } 74 | else if(args[i].compare("-out") == 0) 75 | { 76 | if(i == argc - 1) 77 | throw invalid_argument("invalid command\n"); 78 | opt.predict_path = args[++i]; 79 | } 80 | else 81 | { 82 | break; 83 | } 84 | } 85 | return opt; 86 | } 87 | 88 | 89 | int main(int argc, char* argv[]) 90 | { 91 | cin.sync_with_stdio(false); 92 | cout.sync_with_stdio(false); 93 | Option opt; 94 | try 95 | { 96 | opt = parse_option(argv_to_args(argc, argv)); 97 | } 98 | catch(const invalid_argument& e) 99 | { 100 | cout << e.what() << endl; 101 | cout << predict_help() << endl; 102 | return EXIT_FAILURE; 103 | } 104 | 105 | 106 | ifstream f_model(opt.model_path.c_str()); 107 | ofstream f_predict(opt.predict_path.c_str(), ofstream::out); 108 | 109 | ftrl_predictor predictor(opt.class_num, opt.dim, f_model, f_predict); 110 | 111 | pc_frame frame; 112 | frame.init(predictor, opt.threads_num); 113 | frame.run(); 114 | 115 | f_model.close(); 116 | f_predict.close(); 117 | return 0; 118 | } 119 | 120 | -------------------------------------------------------------------------------- /fm_train.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include "src/Frame/pc_frame.h" 5 | #include "src/FTRL/ftrl_trainer.h" 6 | 7 | using namespace std; 8 | 9 | string train_help() 10 | { 11 | return string( 12 | "\nusage: cat sample | ./fm_train_softmax []" 13 | "\n" 14 | "\n" 15 | "options:\n" 16 | "-m : set the output model path\n" 17 | "-cn : set the number of classes\n" 18 | "-dim : k0=use bias, k1=use 1-way interactions, k2=dim of 2-way interactions\tdefault:1,1,8\n" 19 | "-init_stdev : stdev for initialization of 2-way factors\tdefault:0.1\n" 20 | "-w_alpha : w is updated via FTRL, alpha is one of the learning rate parameters\tdefault:0.05\n" 21 | "-w_beta : w is updated via FTRL, beta is one of the learning rate parameters\tdefault:1.0\n" 22 | "-w_l1 : L1 regularization parameter of w\tdefault:0.1\n" 23 | "-w_l2 : L2 regularization parameter of w\tdefault:5.0\n" 24 | "-v_alpha : v is updated via FTRL, alpha is one of the learning rate parameters\tdefault:0.05\n" 25 | "-v_beta : v is updated via FTRL, beta is one of the learning rate parameters\tdefault:1.0\n" 26 | "-v_l1 : L1 regularization parameter of v\tdefault:0.1\n" 27 | "-v_l2 : L2 regularization parameter of v\tdefault:5.0\n" 28 | "-core : set the number of threads\tdefault:1\n" 29 | "-im : set the initial value of model\n" 30 | ); 31 | } 32 | 33 | vector argv_to_args(int argc, char* argv[]) 34 | { 35 | vector args; 36 | for(int i = 1; i < argc; ++i) 37 | { 38 | args.push_back(string(argv[i])); 39 | } 40 | return args; 41 | } 42 | 43 | 44 | int main(int argc, char* argv[]) 45 | { 46 | cin.sync_with_stdio(false); 47 | cout.sync_with_stdio(false); 48 | srand(time(NULL)); 49 | trainer_option opt; 50 | try 51 | { 52 | opt.parse_option(argv_to_args(argc, argv)); 53 | } 54 | catch(const invalid_argument& e) 55 | { 56 | cout << "invalid_argument:" << e.what() << endl; 57 | cout << train_help() << endl; 58 | return EXIT_FAILURE; 59 | } 60 | 61 | ftrl_trainer trainer(opt); 62 | 63 | if(opt.b_init) 64 | { 65 | ifstream f_temp(opt.init_m_path.c_str()); 66 | if(!trainer.loadModel(f_temp)) 67 | { 68 | cout << "wrong model" << endl; 69 | return EXIT_FAILURE; 70 | } 71 | f_temp.close(); 72 | } 73 | 74 | pc_frame frame; 75 | frame.init(trainer, opt.threads_num); 76 | frame.run(); 77 | 78 | ofstream f_model(opt.model_path.c_str(), ofstream::out); 79 | trainer.outputModel(f_model); 80 | f_model.close(); 81 | 82 | return 0; 83 | } 84 | 85 | 86 | -------------------------------------------------------------------------------- /src/FTRL/ftrl_model.h: -------------------------------------------------------------------------------- 1 | #ifndef FTRL_MODEL_H_ 2 | #define FTRL_MODEL_H_ 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include "../Utils/utils.h" 11 | 12 | using namespace std; 13 | 14 | 15 | //每一个特征维度的每一个类别的模型单元 16 | class ftrl_model_class_unit 17 | { 18 | public: 19 | double wi; 20 | double w_ni; 21 | double w_zi; 22 | vector vi; 23 | vector v_ni; 24 | vector v_zi; 25 | public: 26 | ftrl_model_class_unit(int factor_num, double v_mean, double v_stdev) 27 | { 28 | wi = 0.0; 29 | w_ni = 0.0; 30 | w_zi = 0.0; 31 | vi.resize(factor_num); 32 | v_ni.resize(factor_num); 33 | v_zi.resize(factor_num); 34 | for(int f = 0; f < factor_num; ++f) 35 | { 36 | vi[f] = utils::gaussian(v_mean, v_stdev); 37 | v_ni[f] = 0.0; 38 | v_zi[f] = 0.0; 39 | } 40 | } 41 | 42 | ftrl_model_class_unit(int factor_num, const vector& modelLineSeg, int start) 43 | { 44 | vi.resize(factor_num); 45 | v_ni.resize(factor_num); 46 | v_zi.resize(factor_num); 47 | wi = stod(modelLineSeg[start + 1]); 48 | w_ni = stod(modelLineSeg[start + 2 + factor_num]); 49 | w_zi = stod(modelLineSeg[start + 3 + factor_num]); 50 | for(int f = 0; f < factor_num; ++f) 51 | { 52 | vi[f] = stod(modelLineSeg[start + 2 + f]); 53 | v_ni[f] = stod(modelLineSeg[start + 4 + factor_num + f]); 54 | v_zi[f] = stod(modelLineSeg[start + 4 + 2 * factor_num + f]); 55 | } 56 | } 57 | 58 | friend inline ostream& operator <<(ostream& os, const ftrl_model_class_unit& mcu) 59 | { 60 | os << mcu.wi; 61 | for(int f = 0; f < mcu.vi.size(); ++f) 62 | { 63 | os << " " << mcu.vi[f]; 64 | } 65 | os << " " << mcu.w_ni << " " << mcu.w_zi; 66 | for(int f = 0; f < mcu.v_ni.size(); ++f) 67 | { 68 | os << " " << mcu.v_ni[f]; 69 | } 70 | for(int f = 0; f < mcu.v_zi.size(); ++f) 71 | { 72 | os << " " << mcu.v_zi[f]; 73 | } 74 | return os; 75 | } 76 | }; 77 | 78 | 79 | 80 | //每一个特征维度的模型单元 81 | class ftrl_model_unit 82 | { 83 | public: 84 | int class_num; 85 | vector mcu; 86 | mutex mtx; 87 | public: 88 | ftrl_model_unit(int cn, int factor_num, double v_mean, double v_stdev) 89 | { 90 | class_num = cn; 91 | for(int i = 0; i < class_num; ++i) 92 | { 93 | mcu.push_back(ftrl_model_class_unit(factor_num, v_mean, v_stdev)); 94 | } 95 | } 96 | 97 | ftrl_model_unit(int cn, int factor_num, const vector& modelLineSeg) 98 | { 99 | class_num = cn; 100 | for(int i = 0; i < class_num; ++i) 101 | { 102 | int start = i * (3 + 3 * factor_num); 103 | mcu.push_back(ftrl_model_class_unit(factor_num, modelLineSeg, start)); 104 | } 105 | } 106 | 107 | friend inline ostream& operator <<(ostream& os, const ftrl_model_unit& mu) 108 | { 109 | for(int i = 0; i < mu.class_num; ++i) 110 | { 111 | if(i > 0) os << " "; 112 | os << mu.mcu[i]; 113 | } 114 | return os; 115 | } 116 | }; 117 | 118 | 119 | 120 | 121 | class ftrl_model 122 | { 123 | public: 124 | ftrl_model_unit* muBias; 125 | unordered_map muMap; 126 | 127 | int class_num; 128 | int factor_num; 129 | double init_stdev; 130 | double init_mean; 131 | 132 | public: 133 | ftrl_model(int _num_class, int _factor_num); 134 | ftrl_model(int _num_class, int _factor_num, double _mean, double _stdev); 135 | ftrl_model_unit* getOrInitModelUnit(string index); 136 | ftrl_model_unit* getOrInitModelUnitBias(); 137 | 138 | vector predict(const vector >& x, const vector& bias, vector& theta, vector >& sum); 139 | vector getScore(const vector >& x, const vector& bias, unordered_map& theta); 140 | void outputModel(ofstream& out); 141 | bool loadModel(ifstream& in); 142 | void debugPrintModel(); 143 | 144 | private: 145 | double get_wi(unordered_map& theta, const string& index, int classIndex); 146 | double get_vif(unordered_map& theta, const string& index, int f, int classIndex); 147 | private: 148 | mutex mtx; 149 | mutex mtx_bias; 150 | }; 151 | 152 | 153 | ftrl_model::ftrl_model(int _class_num, int _factor_num) 154 | { 155 | class_num = _class_num; 156 | factor_num = _factor_num; 157 | init_mean = 0.0; 158 | init_stdev = 0.0; 159 | muBias = NULL; 160 | } 161 | 162 | ftrl_model::ftrl_model(int _class_num, int _factor_num, double _mean, double _stdev) 163 | { 164 | class_num = _class_num; 165 | factor_num = _factor_num; 166 | init_mean = _mean; 167 | init_stdev = _stdev; 168 | muBias = NULL; 169 | } 170 | 171 | 172 | ftrl_model_unit* ftrl_model::getOrInitModelUnit(string index) 173 | { 174 | unordered_map::iterator iter = muMap.find(index); 175 | if(iter == muMap.end()) 176 | { 177 | mtx.lock(); 178 | ftrl_model_unit* pMU = new ftrl_model_unit(class_num, factor_num, init_mean, init_stdev); 179 | muMap.insert(make_pair(index, pMU)); 180 | mtx.unlock(); 181 | return pMU; 182 | } 183 | else 184 | { 185 | return iter->second; 186 | } 187 | } 188 | 189 | 190 | ftrl_model_unit* ftrl_model::getOrInitModelUnitBias() 191 | { 192 | if(NULL == muBias) 193 | { 194 | mtx_bias.lock(); 195 | muBias = new ftrl_model_unit(class_num, 0, init_mean, init_stdev); 196 | mtx_bias.unlock(); 197 | } 198 | return muBias; 199 | } 200 | 201 | 202 | vector ftrl_model::predict(const vector >& x, const vector& bias, vector& theta, vector >& sum) 203 | { 204 | vector resVec(class_num); 205 | for(int k = 0; k < class_num; ++k) 206 | { 207 | double result = 0; 208 | result += bias[k]; 209 | for(int i = 0; i < x.size(); ++i) 210 | { 211 | result += theta[i]->mcu[k].wi * x[i].second; 212 | } 213 | double sum_sqr, d; 214 | for(int f = 0; f < factor_num; ++f) 215 | { 216 | sum[k][f] = sum_sqr = 0.0; 217 | for(int i = 0; i < x.size(); ++i) 218 | { 219 | d = theta[i]->mcu[k].vi[f] * x[i].second; 220 | sum[k][f] += d; 221 | sum_sqr += d * d; 222 | } 223 | result += 0.5 * (sum[k][f] * sum[k][f] - sum_sqr); 224 | } 225 | resVec[k] = result; 226 | } 227 | return resVec; 228 | } 229 | 230 | 231 | vector ftrl_model::getScore(const vector >& x, const vector& bias, unordered_map& theta) 232 | { 233 | vector scoreVec(class_num); 234 | double denominator = 0.0; 235 | double maxResult = numeric_limits::lowest(); 236 | for(int k = 0; k < class_num; ++k) 237 | { 238 | double result = 0; 239 | result += bias[k]; 240 | for(int i = 0; i < x.size(); ++i) 241 | { 242 | result += get_wi(theta, x[i].first, k) * x[i].second; 243 | } 244 | double sum, sum_sqr, d; 245 | for(int f = 0; f < factor_num; ++f) 246 | { 247 | sum = sum_sqr = 0.0; 248 | for(int i = 0; i < x.size(); ++i) 249 | { 250 | d = get_vif(theta, x[i].first, f, k) * x[i].second; 251 | sum += d; 252 | sum_sqr += d * d; 253 | } 254 | result += 0.5 * (sum * sum - sum_sqr); 255 | } 256 | scoreVec[k] = result; 257 | if(result > maxResult) maxResult = result; 258 | } 259 | for(int k = 0; k < class_num; ++k) 260 | { 261 | scoreVec[k] -= maxResult; 262 | scoreVec[k] = exp(scoreVec[k]); 263 | denominator += scoreVec[k]; 264 | } 265 | for(int k = 0; k < class_num; ++k) 266 | { 267 | scoreVec[k] /= denominator; 268 | } 269 | return scoreVec; 270 | } 271 | 272 | 273 | double ftrl_model::get_wi(unordered_map& theta, const string& index, int classIndex) 274 | { 275 | unordered_map::iterator iter = theta.find(index); 276 | if(iter == theta.end()) 277 | { 278 | return 0.0; 279 | } 280 | else 281 | { 282 | return iter->second->mcu[classIndex].wi; 283 | } 284 | } 285 | 286 | 287 | double ftrl_model::get_vif(unordered_map& theta, const string& index, int f, int classIndex) 288 | { 289 | unordered_map::iterator iter = theta.find(index); 290 | if(iter == theta.end()) 291 | { 292 | return 0.0; 293 | } 294 | else 295 | { 296 | return iter->second->mcu[classIndex].vi[f]; 297 | } 298 | } 299 | 300 | 301 | void ftrl_model::outputModel(ofstream& out) 302 | { 303 | out << "bias " << *muBias << endl; 304 | for(unordered_map::iterator iter = muMap.begin(); iter != muMap.end(); ++iter) 305 | { 306 | out << iter->first << " " << *(iter->second) << endl; 307 | } 308 | } 309 | 310 | 311 | void ftrl_model::debugPrintModel() 312 | { 313 | cout << "bias " << *muBias << endl; 314 | for(unordered_map::iterator iter = muMap.begin(); iter != muMap.end(); ++iter) 315 | { 316 | cout << iter->first << " " << *(iter->second) << endl; 317 | } 318 | } 319 | 320 | 321 | bool ftrl_model::loadModel(ifstream& in) 322 | { 323 | string line; 324 | if(!getline(in, line)) 325 | { 326 | return false; 327 | } 328 | vector strVec; 329 | utils::splitString(line, ' ', &strVec); 330 | if(strVec.size() != 1 + 3 * class_num) 331 | { 332 | return false; 333 | } 334 | muBias = new ftrl_model_unit(class_num, 0, strVec); 335 | const int SEG_NUM = 1 + (3 + 3 * factor_num) * class_num; 336 | while(getline(in, line)) 337 | { 338 | strVec.clear(); 339 | utils::splitString(line, ' ', &strVec); 340 | if(strVec.size() != SEG_NUM) 341 | { 342 | return false; 343 | } 344 | string& index = strVec[0]; 345 | ftrl_model_unit* pMU = new ftrl_model_unit(class_num, factor_num, strVec); 346 | muMap[index] = pMU; 347 | } 348 | return true; 349 | } 350 | 351 | 352 | 353 | #endif /*FTRL_MODEL_H_*/ 354 | -------------------------------------------------------------------------------- /src/FTRL/ftrl_predictor.h: -------------------------------------------------------------------------------- 1 | #ifndef FTRL_PREDICTOR_H_ 2 | #define FTRL_PREDICTOR_H_ 3 | 4 | #include "../Frame/pc_frame.h" 5 | #include "ftrl_model.h" 6 | #include "../Sample/fm_sample.h" 7 | 8 | 9 | class ftrl_predictor : public pc_task 10 | { 11 | public: 12 | ftrl_predictor(int _class_num, int _factor_num, ifstream& _fModel, ofstream& _fPredict); 13 | virtual void run_task(vector& dataBuffer); 14 | private: 15 | int class_num; 16 | ftrl_model* pModel; 17 | ofstream& fPredict; 18 | mutex outMtx; 19 | }; 20 | 21 | 22 | ftrl_predictor::ftrl_predictor(int _class_num, int _factor_num, ifstream& _fModel, ofstream& _fPredict):fPredict(_fPredict) 23 | { 24 | class_num = _class_num; 25 | pModel = new ftrl_model(_class_num, _factor_num); 26 | if(!pModel->loadModel(_fModel)) 27 | { 28 | cout << "load model error!" << endl; 29 | exit(-1); 30 | } 31 | } 32 | 33 | void ftrl_predictor::run_task(vector& dataBuffer) 34 | { 35 | vector outputVec(dataBuffer.size()); 36 | vector biasVec(class_num); 37 | for(int i = 0; i < dataBuffer.size(); ++i) 38 | { 39 | fm_sample sample(dataBuffer[i], class_num); 40 | for(int k = 0; k < class_num; ++k) 41 | { 42 | biasVec[k] = pModel->muBias->mcu[k].wi; 43 | } 44 | vector scoreVec = pModel->getScore(sample.x, biasVec, pModel->muMap); 45 | outputVec[i] = to_string(sample.y); 46 | for(int k = 0; k < class_num; ++k) 47 | { 48 | outputVec[i] += " " + to_string(scoreVec[k]); 49 | } 50 | } 51 | outMtx.lock(); 52 | for(int i = 0; i < outputVec.size(); ++i) 53 | { 54 | fPredict << outputVec[i] << endl; 55 | } 56 | outMtx.unlock(); 57 | } 58 | 59 | 60 | #endif /*FTRL_PREDICTOR_H_*/ 61 | -------------------------------------------------------------------------------- /src/FTRL/ftrl_trainer.h: -------------------------------------------------------------------------------- 1 | #ifndef FTRL_TRAINER_H_ 2 | #define FTRL_TRAINER_H_ 3 | 4 | #include "../Frame/pc_frame.h" 5 | #include "ftrl_model.h" 6 | #include "../Sample/fm_sample.h" 7 | #include "../Utils/utils.h" 8 | 9 | 10 | struct trainer_option 11 | { 12 | trainer_option() : k0(true), k1(true), factor_num(8), init_mean(0.0), init_stdev(0.1), w_alpha(0.05), w_beta(1.0), w_l1(0.1), w_l2(5.0), 13 | v_alpha(0.05), v_beta(1.0), v_l1(0.1), v_l2(5.0), 14 | threads_num(1), b_init(false), class_num(0) {} 15 | string model_path, init_m_path; 16 | double init_mean, init_stdev; 17 | double w_alpha, w_beta, w_l1, w_l2; 18 | double v_alpha, v_beta, v_l1, v_l2; 19 | int threads_num, factor_num, class_num; 20 | bool k0, k1, b_init; 21 | 22 | void parse_option(const vector& args) 23 | { 24 | int argc = args.size(); 25 | if(0 == argc) throw invalid_argument("invalid command\n"); 26 | for(int i = 0; i < argc; ++i) 27 | { 28 | if(args[i].compare("-m") == 0) 29 | { 30 | if(i == argc - 1) 31 | throw invalid_argument("invalid command\n"); 32 | model_path = args[++i]; 33 | } 34 | else if(args[i].compare("-dim") == 0) 35 | { 36 | if(i == argc - 1) 37 | throw invalid_argument("invalid command\n"); 38 | vector strVec; 39 | string tmpStr = args[++i]; 40 | utils::splitString(tmpStr, ',', &strVec); 41 | if(strVec.size() != 3) 42 | throw invalid_argument("invalid command\n"); 43 | k0 = 0 == stoi(strVec[0]) ? false : true; 44 | k1 = 0 == stoi(strVec[1]) ? false : true; 45 | factor_num = stoi(strVec[2]); 46 | } 47 | else if(args[i].compare("-init_stdev") == 0) 48 | { 49 | if(i == argc - 1) 50 | throw invalid_argument("invalid command\n"); 51 | init_stdev = stod(args[++i]); 52 | } 53 | else if(args[i].compare("-w_alpha") == 0) 54 | { 55 | if(i == argc - 1) 56 | throw invalid_argument("invalid command\n"); 57 | w_alpha = stod(args[++i]); 58 | } 59 | else if(args[i].compare("-w_beta") == 0) 60 | { 61 | if(i == argc - 1) 62 | throw invalid_argument("invalid command\n"); 63 | w_beta = stod(args[++i]); 64 | } 65 | else if(args[i].compare("-w_l1") == 0) 66 | { 67 | if(i == argc - 1) 68 | throw invalid_argument("invalid command\n"); 69 | w_l1 = stod(args[++i]); 70 | } 71 | else if(args[i].compare("-w_l2") == 0) 72 | { 73 | if(i == argc - 1) 74 | throw invalid_argument("invalid command\n"); 75 | w_l2 = stod(args[++i]); 76 | } 77 | else if(args[i].compare("-v_alpha") == 0) 78 | { 79 | if(i == argc - 1) 80 | throw invalid_argument("invalid command\n"); 81 | v_alpha = stod(args[++i]); 82 | } 83 | else if(args[i].compare("-v_beta") == 0) 84 | { 85 | if(i == argc - 1) 86 | throw invalid_argument("invalid command\n"); 87 | v_beta = stod(args[++i]); 88 | } 89 | else if(args[i].compare("-v_l1") == 0) 90 | { 91 | if(i == argc - 1) 92 | throw invalid_argument("invalid command\n"); 93 | v_l1 = stod(args[++i]); 94 | } 95 | else if(args[i].compare("-v_l2") == 0) 96 | { 97 | if(i == argc - 1) 98 | throw invalid_argument("invalid command\n"); 99 | v_l2 = stod(args[++i]); 100 | } 101 | else if(args[i].compare("-core") == 0) 102 | { 103 | if(i == argc - 1) 104 | throw invalid_argument("invalid command\n"); 105 | threads_num = stoi(args[++i]); 106 | } 107 | else if(args[i].compare("-im") == 0) 108 | { 109 | if(i == argc - 1) 110 | throw invalid_argument("invalid command\n"); 111 | init_m_path = args[++i]; 112 | b_init = true; //if im field exits , that means b_init = true ! 113 | } 114 | else if(args[i].compare("-cn") == 0) 115 | { 116 | if(i == argc - 1) 117 | throw invalid_argument("invalid command\n"); 118 | class_num = stoi(args[++i]); 119 | } 120 | else 121 | { 122 | throw invalid_argument("invalid command\n"); 123 | break; 124 | } 125 | } 126 | } 127 | 128 | }; 129 | 130 | 131 | class ftrl_trainer : public pc_task 132 | { 133 | public: 134 | ftrl_trainer(const trainer_option& opt); 135 | virtual void run_task(vector& dataBuffer); 136 | bool loadModel(ifstream& in); 137 | void outputModel(ofstream& out); 138 | private: 139 | void train(int y, const vector >& x); 140 | private: 141 | ftrl_model* pModel; 142 | double w_alpha, w_beta, w_l1, w_l2; 143 | double v_alpha, v_beta, v_l1, v_l2; 144 | bool k0; 145 | bool k1; 146 | }; 147 | 148 | 149 | ftrl_trainer::ftrl_trainer(const trainer_option& opt) 150 | { 151 | w_alpha = opt.w_alpha; 152 | w_beta = opt.w_beta; 153 | w_l1 = opt.w_l1; 154 | w_l2 = opt.w_l2; 155 | v_alpha = opt.v_alpha; 156 | v_beta = opt.v_beta; 157 | v_l1 = opt.v_l1; 158 | v_l2 = opt.v_l2; 159 | k0 = opt.k0; 160 | k1 = opt.k1; 161 | pModel = new ftrl_model(opt.class_num, opt.factor_num, opt.init_mean, opt.init_stdev); 162 | } 163 | 164 | void ftrl_trainer::run_task(vector& dataBuffer) 165 | { 166 | int class_num = pModel->class_num; 167 | for(int i = 0; i < dataBuffer.size(); ++i) 168 | { 169 | fm_sample sample(dataBuffer[i], class_num); 170 | train(sample.y, sample.x); 171 | } 172 | } 173 | 174 | 175 | bool ftrl_trainer::loadModel(ifstream& in) 176 | { 177 | return pModel->loadModel(in); 178 | } 179 | 180 | 181 | void ftrl_trainer::outputModel(ofstream& out) 182 | { 183 | return pModel->outputModel(out); 184 | } 185 | 186 | 187 | //输入一个样本,更新参数 188 | void ftrl_trainer::train(int y, const vector >& x) 189 | { 190 | ftrl_model_unit* thetaBias = pModel->getOrInitModelUnitBias(); 191 | vector theta(x.size(), NULL); 192 | int xLen = x.size(); 193 | for(int i = 0; i < xLen; ++i) 194 | { 195 | const string& index = x[i].first; 196 | theta[i] = pModel->getOrInitModelUnit(index); 197 | } 198 | //update w via FTRL 199 | for(int i = 0; i <= xLen; ++i) 200 | { 201 | ftrl_model_unit& mu = i < xLen ? *(theta[i]) : *thetaBias; 202 | if((i < xLen && k1) || (i == xLen && k0)) 203 | { 204 | mu.mtx.lock(); 205 | for(int k = 0; k < pModel->class_num; ++k) 206 | { 207 | if(fabs(mu.mcu[k].w_zi) <= w_l1) 208 | { 209 | mu.mcu[k].wi = 0.0; 210 | } 211 | else 212 | { 213 | mu.mcu[k].wi = (-1) * 214 | (1 / (w_l2 + (w_beta + sqrt(mu.mcu[k].w_ni)) / w_alpha)) * 215 | (mu.mcu[k].w_zi - utils::sgn(mu.mcu[k].w_zi) * w_l1); 216 | } 217 | } 218 | mu.mtx.unlock(); 219 | } 220 | } 221 | //update v via FTRL 222 | for(int i = 0; i < xLen; ++i) 223 | { 224 | ftrl_model_unit& mu = *(theta[i]); 225 | for(int f = 0; f < pModel->factor_num; ++f) 226 | { 227 | mu.mtx.lock(); 228 | for(int k = 0; k < pModel->class_num; ++k) 229 | { 230 | double& vif = mu.mcu[k].vi[f]; 231 | double& v_nif = mu.mcu[k].v_ni[f]; 232 | double& v_zif = mu.mcu[k].v_zi[f]; 233 | if(v_nif > 0) 234 | { 235 | if(fabs(v_zif) <= v_l1) 236 | { 237 | vif = 0.0; 238 | } 239 | else 240 | { 241 | vif = (-1) * 242 | (1 / (v_l2 + (v_beta + sqrt(v_nif)) / v_alpha)) * 243 | (v_zif - utils::sgn(v_zif) * v_l1); 244 | } 245 | } 246 | } 247 | mu.mtx.unlock(); 248 | } 249 | } 250 | vector > sum(pModel->class_num); 251 | vector biasVec(pModel->class_num); 252 | for(int k = 0; k < pModel->class_num; ++k) 253 | { 254 | sum[k].resize(pModel->factor_num); 255 | biasVec[k] = thetaBias->mcu[k].wi; 256 | } 257 | vector p = pModel->predict(x, biasVec, theta, sum); 258 | double max_p = numeric_limits::lowest(); 259 | for(int k = 0; k < pModel->class_num; ++k) 260 | { 261 | if(p[k] > max_p) max_p = p[k]; 262 | } 263 | double denominator = 0.0; 264 | for(int k = 0; k < pModel->class_num; ++k) 265 | { 266 | p[k] -= max_p; 267 | p[k] = exp(p[k]); 268 | denominator += p[k]; 269 | } 270 | for(int k = 0; k < pModel->class_num; ++k) 271 | { 272 | p[k] /= denominator; 273 | } 274 | vector mult(pModel->class_num); 275 | for(int k = 0; k < pModel->class_num; ++k) 276 | { 277 | int yk = y == (k + 1) ? 1 : 0; 278 | mult[k] = p[k] - yk; 279 | } 280 | //update w_n, w_z 281 | for(int i = 0; i <= xLen; ++i) 282 | { 283 | ftrl_model_unit& mu = i < xLen ? *(theta[i]) : *thetaBias; 284 | double xi = i < xLen ? x[i].second : 1.0; 285 | if((i < xLen && k1) || (i == xLen && k0)) 286 | { 287 | mu.mtx.lock(); 288 | for(int k = 0; k < pModel->class_num; ++k) 289 | { 290 | double w_gi = mult[k] * xi; 291 | double w_si = 1 / w_alpha * (sqrt(mu.mcu[k].w_ni + w_gi * w_gi) - sqrt(mu.mcu[k].w_ni)); 292 | mu.mcu[k].w_zi += w_gi - w_si * mu.mcu[k].wi; 293 | mu.mcu[k].w_ni += w_gi * w_gi; 294 | } 295 | mu.mtx.unlock(); 296 | } 297 | } 298 | //update v_n, v_z 299 | for(int i = 0; i < xLen; ++i) 300 | { 301 | ftrl_model_unit& mu = *(theta[i]); 302 | const double& xi = x[i].second; 303 | for(int f = 0; f < pModel->factor_num; ++f) 304 | { 305 | mu.mtx.lock(); 306 | for(int k = 0; k < pModel->class_num; ++k) 307 | { 308 | double& vif = mu.mcu[k].vi[f]; 309 | double& v_nif = mu.mcu[k].v_ni[f]; 310 | double& v_zif = mu.mcu[k].v_zi[f]; 311 | double v_gif = mult[k] * (sum[k][f] * xi - vif * xi * xi); 312 | double v_sif = 1 / v_alpha * (sqrt(v_nif + v_gif * v_gif) - sqrt(v_nif)); 313 | v_zif += v_gif - v_sif * vif; 314 | v_nif += v_gif * v_gif; 315 | } 316 | mu.mtx.unlock(); 317 | } 318 | } 319 | ////////// 320 | //pModel->debugPrintModel(); 321 | ////////// 322 | } 323 | 324 | 325 | #endif /*FTRL_TRAINER_H_*/ 326 | -------------------------------------------------------------------------------- /src/Frame/Makefile: -------------------------------------------------------------------------------- 1 | all: 2 | g++ test_main.cpp pc_frame.cpp -I . -std=c++0x -o test_main -lpthread 3 | -------------------------------------------------------------------------------- /src/Frame/pc_frame.cpp: -------------------------------------------------------------------------------- 1 | #include "pc_frame.h" 2 | 3 | bool pc_frame::init(pc_task& task, int t_num, int buf_size, int log_num) 4 | { 5 | pTask = &task; 6 | threadNum = t_num; 7 | bufSize = buf_size; 8 | logNum = log_num; 9 | sem_init(&semPro, 0, 1); 10 | sem_init(&semCon, 0, 0); 11 | threadVec.clear(); 12 | threadVec.push_back(thread(&pc_frame::proThread, this)); 13 | for(int i = 0; i < threadNum; ++i) 14 | { 15 | threadVec.push_back(thread(&pc_frame::conThread, this)); 16 | } 17 | return true; 18 | } 19 | 20 | 21 | void pc_frame::run() 22 | { 23 | for(int i = 0; i < threadVec.size(); ++i) 24 | { 25 | threadVec[i].join(); 26 | } 27 | } 28 | 29 | void pc_frame::proThread() 30 | { 31 | string line; 32 | int line_num = 0; 33 | int i = 0; 34 | bool finished_flag = false; 35 | while(true) 36 | { 37 | sem_wait(&semPro); 38 | bufMtx.lock(); 39 | for(i = 0; i < bufSize; ++i) 40 | { 41 | if(!getline(cin, line)) 42 | { 43 | finished_flag = true; 44 | break; 45 | } 46 | line_num++; 47 | buffer.push(line); 48 | if(line_num%logNum == 0) 49 | { 50 | cout << line_num << " lines have finished" << endl; 51 | } 52 | } 53 | bufMtx.unlock(); 54 | sem_post(&semCon); 55 | if(finished_flag) 56 | { 57 | break; 58 | } 59 | } 60 | } 61 | 62 | 63 | void pc_frame::conThread(){ 64 | bool finished_flag = false; 65 | vector input_vec; 66 | input_vec.reserve(bufSize); 67 | while(true) 68 | { 69 | input_vec.clear(); 70 | sem_wait(&semCon); 71 | bufMtx.lock(); 72 | for(int i = 0; i < bufSize; ++i) 73 | { 74 | if(buffer.empty()) 75 | { 76 | finished_flag = true; 77 | break; 78 | } 79 | input_vec.push_back(buffer.front()); 80 | buffer.pop(); 81 | } 82 | bufMtx.unlock(); 83 | sem_post(&semPro); 84 | pTask->run_task(input_vec); 85 | if(finished_flag) 86 | break; 87 | } 88 | sem_post(&semCon); 89 | } 90 | 91 | -------------------------------------------------------------------------------- /src/Frame/pc_frame.h: -------------------------------------------------------------------------------- 1 | #ifndef PC_FRAME_H 2 | #define PC_FRAME_H 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include "pc_task.h" 15 | 16 | using namespace std; 17 | 18 | class pc_frame 19 | { 20 | public: 21 | pc_frame(){} 22 | bool init(pc_task& task, int t_num, int buf_size = 5000, int log_num = 200000); 23 | void run(); 24 | 25 | private: 26 | int threadNum; 27 | pc_task* pTask; 28 | mutex bufMtx; 29 | sem_t semPro, semCon; 30 | queue buffer; 31 | vector threadVec; 32 | int bufSize; 33 | int logNum; 34 | void proThread(); 35 | void conThread(); 36 | }; 37 | 38 | 39 | #endif //PC_FRAME_H 40 | -------------------------------------------------------------------------------- /src/Frame/pc_task.h: -------------------------------------------------------------------------------- 1 | #ifndef PC_TASK_H 2 | #define PC_TASK_H 3 | 4 | #include 5 | #include 6 | 7 | using std::string; 8 | using std::vector; 9 | 10 | class pc_task 11 | { 12 | public: 13 | pc_task(){} 14 | virtual void run_task(vector& dataBuffer) = 0; 15 | }; 16 | 17 | 18 | #endif //PC_TASK_H 19 | -------------------------------------------------------------------------------- /src/Frame/test_main.cpp: -------------------------------------------------------------------------------- 1 | #include "pc_frame.h" 2 | #include "test_task.h" 3 | 4 | int main() 5 | { 6 | test_task task; 7 | pc_frame frame; 8 | frame.init(task, 2, 5, 5); 9 | frame.run(); 10 | return 0; 11 | } 12 | 13 | -------------------------------------------------------------------------------- /src/Frame/test_task.h: -------------------------------------------------------------------------------- 1 | #ifndef TEST_TASK_H 2 | #define TEST_TASK_H 3 | 4 | #include 5 | #include "pc_task.h" 6 | using namespace std; 7 | 8 | class test_task : public pc_task 9 | { 10 | public: 11 | test_task(){} 12 | virtual void run_task(vector& dataBuffer) 13 | { 14 | cout << "==========\n"; 15 | for(int i = 0; i < dataBuffer.size(); ++i) 16 | { 17 | cout << dataBuffer[i] << endl; 18 | } 19 | cout << "**********\n"; 20 | } 21 | }; 22 | 23 | 24 | #endif //TEST_TASK_H 25 | -------------------------------------------------------------------------------- /src/Sample/fm_sample.h: -------------------------------------------------------------------------------- 1 | #ifndef FM_SAMPLE_H_ 2 | #define FM_SAMPLE_H_ 3 | 4 | #include 5 | #include 6 | 7 | using namespace std; 8 | 9 | const string spliter = " "; 10 | const string innerSpliter = ":"; 11 | 12 | 13 | class fm_sample 14 | { 15 | public: 16 | int y; 17 | vector > x; 18 | fm_sample(const string& line, int class_num); 19 | }; 20 | 21 | 22 | fm_sample::fm_sample(const string& line, int class_num) 23 | { 24 | this->x.clear(); 25 | size_t posb = line.find_first_not_of(spliter, 0); 26 | size_t pose = line.find_first_of(spliter, posb); 27 | int label = atoi(line.substr(posb, pose-posb).c_str()); 28 | if(label < 1 || label > class_num) 29 | { 30 | cout << "wrong line input, label out of range\n" << line << endl; 31 | throw "wrong line input"; 32 | } 33 | this->y = label; 34 | string key; 35 | double value; 36 | while(pose < line.size()) 37 | { 38 | posb = line.find_first_not_of(spliter, pose); 39 | if(posb == string::npos) 40 | { 41 | break; 42 | } 43 | pose = line.find_first_of(innerSpliter, posb); 44 | if(pose == string::npos) 45 | { 46 | cout << "wrong line input\n" << line << endl; 47 | throw "wrong line input"; 48 | } 49 | key = line.substr(posb, pose-posb); 50 | posb = pose + 1; 51 | if(posb >= line.size()) 52 | { 53 | cout << "wrong line input\n" << line << endl; 54 | throw "wrong line input"; 55 | } 56 | pose = line.find_first_of(spliter, posb); 57 | value = stod(line.substr(posb, pose-posb)); 58 | if(value != 0) 59 | { 60 | this->x.push_back(make_pair(key, value)); 61 | } 62 | } 63 | } 64 | 65 | 66 | #endif /*FM_SAMPLE_H_*/ 67 | -------------------------------------------------------------------------------- /src/Utils/utils.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include "utils.h" 4 | 5 | const double kPrecision = 0.0000000001; 6 | void utils::splitString(string& line, char delimiter, vector* r) 7 | { 8 | int begin = 0; 9 | for(int i = 0; i < line.size(); ++i) 10 | { 11 | if(line[i] == delimiter) 12 | { 13 | (*r).push_back(line.substr(begin, i - begin)); 14 | begin = i + 1; 15 | } 16 | } 17 | if(begin < line.size()) 18 | (*r).push_back(line.substr(begin, line.size() - begin)); 19 | } 20 | 21 | 22 | int utils::sgn(double x) 23 | { 24 | if(x > kPrecision) 25 | return 1; 26 | else 27 | return -1; 28 | } 29 | 30 | 31 | double utils::uniform() 32 | { 33 | return rand()/((double)RAND_MAX + 1.0); 34 | } 35 | 36 | 37 | double utils::gaussian() 38 | { 39 | double u,v, x, y, Q; 40 | do 41 | { 42 | do 43 | { 44 | u = uniform(); 45 | } while (u == 0.0); 46 | 47 | v = 1.7156 * (uniform() - 0.5); 48 | x = u - 0.449871; 49 | y = fabs(v) + 0.386595; 50 | Q = x * x + y * (0.19600 * y - 0.25472 * x); 51 | } while (Q >= 0.27597 && (Q > 0.27846 || v * v > -4.0 * u * u * log(u))); 52 | return v / u; 53 | } 54 | 55 | 56 | double utils::gaussian(double mean, double stdev) { 57 | if(0.0 == stdev) 58 | { 59 | return mean; 60 | } 61 | else 62 | { 63 | return mean + stdev * gaussian(); 64 | } 65 | } 66 | 67 | -------------------------------------------------------------------------------- /src/Utils/utils.h: -------------------------------------------------------------------------------- 1 | #ifndef FTRL_UTILS_H 2 | #define FTRL_UTILS_H 3 | 4 | 5 | #include 6 | #include 7 | using namespace std; 8 | 9 | class utils 10 | { 11 | public: 12 | void static splitString(string& line, char delimiter, vector* r); 13 | int static sgn(double x); 14 | double static uniform(); 15 | double static gaussian(); 16 | double static gaussian(double mean, double stdev); 17 | }; 18 | 19 | 20 | #endif //FTRL_UTILS_H 21 | --------------------------------------------------------------------------------