让程序会判断:归一化、逻辑回归与模型文件
前两篇把数据读进来了也检查过了,这篇写程序真正做判断的部分:怎么把特征值变成 0 到 1 之间的概率。
为什么需要归一化
五个特征的量级差得太远,文件大小是几万甚至几十万,比例是零点几。如果直接拿去加权求和,大的数会把小的数完全压过去,模型等于只看文件大小,另外四个特征成了摆设。
解决办法是把每个特征按比例缩放到相近的范围,公式是:
x = (原始值 - min) / (max - min)min 和 max 是这一列在训练集上的最小值和最大值。比如 file_size 这一列最小 18432、最大 266240,那么 18432 缩放后是 0,266240 是 1,中间的值落在 0 到 1 之间。
关键在于训练和预测必须用同一组 min 和 max,否则标准变了,结果就不可比。所以模型文件里要把它们存下来。
权重和偏置是什么
判断的依据是五个特征的加权和再加一个偏置:
z = bias + w0*x0 + w1*x1 + w2*x2 + w3*x3 + w4*x4权重表示每个特征的重要程度。老师给的模型里,信息熵的权重是 4.319,最大,说明这个特征最能说明问题;文件大小的权重只有 0.166,参考价值最小。权重为负说明这个特征越大反而越不像可疑。
偏置可以理解成判定的门槛。我们这个模型的偏置是 -2.494,是个负数,意味着证据的分数必须先填平这个坑,总分才能回到 0,概率才能到 0.5。相当于默认假定良性,证据足够强才改判可疑。这个值是训练时机器在训练集上试出来的,不是人工拍的。
从总分到概率
总分 z 可能是很大的正数也可能是很小的负数,不方便解读,用 Sigmoid 压到 0 到 1 之间:
p = 1 / (1 + e^(-z))z 为 0 时 p 正好是 0.5,大于 0.5 判可疑,小于 0.5 判良性。
读模型文件
模型文件一共六行,分别是标识、特征数、五个权重、偏置、五个最小值、五个最大值。用fscanf按顺序读,它会自动跳过空白和换行。
FILE*fp=fopen(path,"r");if(fp==NULL){printf("open failed\n");return1;}chartag[64];fscanf(fp,"%63s",tag);if(strcmp(tag,"TEACHMODEL_V1")!=0){printf("bad model\n");fclose(fp);return1;}intk;fscanf(fp,"%d",&k);if(k!=FEATURE_COUNT){printf("bad feature count\n");fclose(fp);return1;}for(intj=0;j<FEATURE_COUNT;j++){fscanf(fp,"%lf",&model->weights[j]);}fscanf(fp,"%lf",&model->bias);for(intj=0;j<FEATURE_COUNT;j++){fscanf(fp,"%lf",&scaler->minv[j]);}for(intj=0;j<FEATURE_COUNT;j++){fscanf(fp,"%lf",&scaler->maxv[j]);}model->trained=1;第一行标识要先验证,遇到不是这个格式的文件直接拒收。第二行的特征数也要核对,防止读到错位的数据。
最后那句model->trained = 1是设一个标记,骨架的菜单里在预测前会检查这个标记,没加载模型就拒绝预测。
预测函数
三步合起来就这几行:
doublez=model->bias;for(intj=0;j<FEATURE_COUNT;j++){doublerange=scaler->maxv[j]-scaler->minv[j];doublex;if(range==0){x=0;}else{x=(raw_features[j]-scaler->minv[j])/range;}z=z+model->weights[j]*x;}return1.0/(1.0+exp(-z));range是这一列的跨度,也就是 max 减 min。要用一个 if 单独判断它是不是 0,因为万一训练集里某列所有值都一样,除零会让程序直接崩掉,约定这种情况归一化结果算 0。
z一开始等于偏置,然后循环里把每个特征的加权结果累加进去,循环结束时就是完整的总分。最后一行是 Sigmoid。
这里有个容易忽略的细节:常数要写成1.0而不是1。C 语言里两个整数相除会直接丢掉小数部分,1 / 2的结果是 0 而不是 0.5。
对答案
老师给了一份sample_expected.csv,里面是八条样本的标准预测概率,第一条是 0.0742812554。
我的程序读sample.csv,加载模型,预测第一条,输出 0.074281,和标准答案一致。这说明归一化、加权求和、Sigmoid 这三步每一步都算对了。
八条样本的结果也符合预期,良性样本的概率都在 0.33 以下,可疑样本都在 0.61 以上,分得比较开。
归一化参数也可以自己算
进阶选做里有一项是fit_scaler,就是不看老师给的参数,自己从训练集里把每列的最小值和最大值找出来。算法是打擂台:先拿第一条样本当初始的最大值和最小值,后面每条样本逐列比较,比当前最小值小就更新最小值,比当前最大值大就更新最大值。
for(intj=0;j<FEATURE_COUNT;j++){scaler->minv[j]=data->samples[0].features[j];scaler->maxv[j]=data->samples[0].features[j];}for(inti=1;i<data->count;i++){for(intj=0;j<FEATURE_COUNT;j++){doublev=data->samples[i].features[j];if(v<scaler->minv[j])scaler->minv[j]=v;if(v>scaler->maxv[j])scaler->maxv[j]=v;}}用train.csv跑出来的结果和模型文件里存的那两行一致,说明计算过程是对的。这里有个原则要注意,只能在训练集上算,不能在测试集上算,标准如果参照了考试卷本身,结果就没有意义了。