数据校验:把坏数据挡在门外
上一篇写完读取函数,这篇写第二个函数validate_dataset。
为什么需要单独一个校验函数
读取函数里其实已经挡了两类问题:拆不出六个数的行会被跳过,超过数组上限会停下来。但数值本身合不合格,读取时是不知道的,比如标签写成了 2、比例写成了 1.3、熵超过了 8,这些行格式是对的,能被正常解析,只是内容不合法。
所以两个函数的分工是:读取函数负责搬进来,校验函数负责检查已经在架子上的货。
检查哪些项
说明文档里写清了每个字段的合法范围,对照着写就行:数据集不能为空,标签只能是 0 或 1,文件大小和字符串数量不能为负,信息熵在 0 到 8 之间,两个比例在 0 到 1 之间。
函数结构是先查空,再用一个循环遍历所有样本,每轮做六项检查。
staticintvalidate_dataset(constDataset*data){if(data->count<=0){printf("Dataset is empty\n");return1;}for(inti=0;i<data->count;i++){if(data->samples[i].label!=0&&data->samples[i].label!=1){printf("Sample %d: bad label %d\n",i,data->samples[i].label);return1;}if(data->samples[i].features[2]<0||data->samples[i].features[2]>8){printf("Sample %d: entropy out of range\n",i);return1;}...}printf("All %d samples valid\n",data->count);return0;}参数带了const,意思是这个函数只读取数据,不修改,校验本来就不该改动数据。
条件要写"不合法"
这是写的时候最容易搞反的地方。判断的目的是"发现不合格的就打回",所以 if 里应该写不合法的条件,而不是合法的条件。
我第一版把文件大小那条写成了features[0] > 0,意思是"只要大小是正数就打回",结果正常数据全被拒了。合法的条件是不为负,反过来就是小于 0 才报错。
if(data->samples[i].features[0]<0){printf("Sample %d: negative file_size\n",i);return1;}比例那两项是范围检查,写法是"小于下界或者大于上界":
if(data->samples[i].features[3]<0||data->samples[i].features[3]>1){printf("Sample %d: byte_00_ratio out of range\n",i);return1;}返回值是给调用者的信号
约定是 0 表示通过,1 表示有问题。空数据集要单独提前返回,不然循环转零圈,走到结尾会打印"全部合格",等于把空数据判成了有效。
调用方可以这么用:
if(validate_dataset(&data)!=0){...}任务书里有一项要求是数据为空时拒绝训练,靠的就是这个返回值。
测试
老师给的data/invalid目录里有十几份故意做坏的文件,正好拿来测。读非法标签的文件,校验报 bad label;读熵超 8 的文件,报 entropy out of range;读比例越界的文件,报 byte_00_ratio out of range;读合法边界的文件,全部通过。
流程是先选读取,再选校验。两个操作分开,才能看出是读取的问题还是校验的问题。
一点体会
条件写反这种错误编译器查不出来,程序照常运行,只是结果全错。写完一个函数就立刻拿不同的输入测一遍,比攒到最后一起调试省事得多。
下一篇写归一化和逻辑回归预测。