我使用OpenCV的SVM实现来二元预测图像特征的重要性。因此,我在正负图像特征上进行训练,并希望得到{0,1}的分类结果。
我遇到的问题是,训练后SVM总是预测具有更高/更大类别标签的类。我可以更改训练数据集的标签,但这个问题依然存在。我仔细检查了生成的标签和训练的cv::Mat矩阵,并未发现任何问题。
以下是我的SVM类及其相关的SVM参数
//填充SVM参数void SVM::setSVMParams(){ params.svm_type = cv::SVM::C_SVC; params.kernel_type = cv::SVM::RBF; params.term_crit = cv::TermCriteria(CV_TERMCRIT_ITER, 100, 1e-6); params_set = true;}//使用给定数据训练SVMvoid SVM::train(cv::Mat train_data, cv::Mat labels){ //如果SVM参数尚未设置,则设置 if (!params_set) { setSVMParams(); } svm.train(train_data, labels, cv::Mat(), cv::Mat(), params);}//根据训练结果,预测给定数据的类别float SVM::predict(cv::Mat sample){ return svm.predict(sample, false);}
以下是负责生成训练数据和相应标签的函数
//根据提供的D阈值创建适当的训练数据和类别标签,用于后续的SVM训练void Matchings::createSVMTrainingObjects(const float t_D, const float positive_label, const float negative_label, bool print_info){ cv::Mat train_data_l((int)matchings_list.size(), 132, CV_32FC1); cv::Mat labels_l((int)matchings_list.size(), 1, CV_32FC1); int num_pos = 0; int num_neg = 0; for (int i = 0; i < matchings_list.size(); i++) { matching_d entry = matchings_list[i]; //重要特征,标签1 if (entry.D > t_D) { labels_l.at<float>(i) = positive_label; num_pos++; } //不重要特征,标签-1 else { labels_l.at<float>(i) = negative_label; num_neg++; } int j = 0; //将特征复制到OpenCV矩阵的当前行 train_data_l.at<float>(i, j++) = entry.feature.x; train_data_l.at<float>(i, j++) = entry.feature.y; train_data_l.at<float>(i, j++) = entry.feature.scale; train_data_l.at<float>(i, j++) = entry.feature.angle; for (int k = 0; k < 128; k++) { train_data_l.at<float>(i, j + k) = entry.feature.vec[k]; } } std::cout << "用于训练:正样本数=" << num_pos << ", 负样本数=" << num_neg << std::endl; train_data = train_data_l; labels = labels_l;}
最后,以下是实际调用SVM预测结果以保留重要图像特征的函数
matchingslist ASIFT::filterFeaturesWithSVM(matchingslist matchings, SVM& svm){ matchingslist new_matchings; for (int i = 0; i < (int)matchings.size(); i++) { cv::Mat first = Utility::keypointToMat(matchings[i].first); cv::Mat second = Utility::keypointToMat(matchings[i].second); //如果两个特征都重要,则保留它们 if (svm.predict(first) == 1.0f && svm.predict(second) == 1.0f) { new_matchings.push_back(matchings[i]); } else { std::cout << "特征已移除" << std::endl; } } return new_matchings;}
回答:
该方法的一个主要问题是您没有设置SVM的超参数,而您使用的是RBF核,因此可能使用了C=1
和gamma=1/d
(或1/mean ||x||^2
),因为这些是大多数SVM实现的默认值。
这些参数对于构建有效模型至关重要。特别是,如果您的C
值过低(1
可能就是,具体取决于数据的许多特征),那么SVM将构建一个简单的模型,简单地总是预测一个类别。
您应该怎么做?您应该检查C
和gamma
的多个值。这些参数的含义是什么?
C
(您的1
)是分类错误的权重 –C
值越大,SVM将更努力地精确学习训练数据,可能导致过拟合。gamma
(您的默认值)是RBF核的2倍方差的倒数。换句话说 –gamma
值越大,高斯分布越小,因此 – 您的算法在几何意义上更“局部”。同样 – 大的gamma
有助于最小化训练误差(偏差),但会导致更高的测试误差(方差)。
正确选择方差-偏差之间的权衡是机器学习技术的关键元素。对于RBF SVM – 您可以通过上述参数来控制。尝试不同的参数,检查训练集误差和测试集误差以了解情况。如果您的训练集误差很大 – 增加C
和/或gamma
。一旦您的训练集误差合适,再看测试集 – 如果误差太大 – 尝试降低值,以此类推。这通常通过内部交叉验证和参数的网格搜索来自动完成。
查看有关模型选择和超参数优化的资料。
此外,您固定了迭代次数
params.term_crit = cv::TermCriteria(CV_TERMCRIT_ITER, 100, 1e-6);
而对于SVM,您永远不应该这样做。让它收敛(至少设置为10,000步),仅100步后,SVM可能尚未接近收敛(因此导致了简单模型)。