使用glmnet正则化逻辑回归时盲测数据预测的维度匹配问题
我最近在用glmnet包做正则化逻辑回归分类,训练过程都很顺利,但在处理盲测数据时卡壳了——盲测集没有类别标签,导致测试数据框比训练集少一列,直接用predict()的时候因为维度不匹配报错。我试着给测试数据加了个随便的类别列临时解决了,但不确定这么做会不会有隐患,下面是我的复现代码:
library(glmnet) example = data.frame(rnorm(20)) colnames(example) = "A" example$B = rnorm(20) example$class = ((example$A + example$B) > 0)*1 testframe = data.frame(rnorm(20)) colnames(testframe) = "A" testframe$B = rnorm(20) x = model.matrix(class ~ ., data = example) y = data.matrix(example$class) # 盲测集不含类别标签,直接预测报错 x.test = as.matrix(testframe) ridge = glmnet(x,y, alpha = 0, family = "binomial", lambda = 0.01789997) ridge.pred = predict(ridge, newx = x.test, s = 0.01789997, type = "class") # 报错信息:Error in cbind2(1, newx) %*% nbeta: Cholmod error 'X and/or Y have wrong dimensions' at file ../MatrixOps/cholmod_sdmult.c, line 90 # 添加类别列后可以正常执行 testframe$class = 0 x.test = model.matrix(class ~ ., data = testframe) ridge.pred2 = predict(ridge, newx = x.test, s = 0.01789997, type = "class")
我的问题:
- a) 这种临时加列的方法安全吗?会不会
predict偷偷用到这个新增的类别列数据? - b) 正确的解决方法应该是什么?
问题解答
a) 临时加列的方法是否安全?
放心,这个方法是安全的,不会影响预测结果。原因很简单:你用model.matrix(class ~ ., data = testframe)生成测试矩阵时,公式class ~ .的含义是用所有其他列来预测class,所以model.matrix会自动把class列当作因变量,不会把它包含到最终的特征矩阵里。你可以自己验证一下,打印x.test看看,里面根本没有class相关的列,只有截距项和A、B列——和训练时的x矩阵结构完全一致,所以predict只会用到正确的特征列,新增的class列相当于“凑数”用的,不会参与计算。
不过这个方法有点“取巧”,不是最规范的做法,所以更推荐下面的标准解法。
b) 正确的解决方法
核心思路是让测试集的特征矩阵和训练集的特征矩阵结构完全一致(包括截距项、特征列的顺序和数量),不需要额外加列。有两种常用方式:
方式一:用训练数据的model.matrix公式来处理测试集
训练时你用了model.matrix(class ~ ., data = example),那测试时可以直接复用这个公式,不需要给测试集加class列,只需要指定data = testframe,同时设置na.action = NULL(避免因为没有因变量报错):
x.test = model.matrix(class ~ ., data = testframe, na.action = NULL)
这样生成的x.test和训练时的x结构完全匹配,直接拿去预测就不会有维度问题了。
方式二:手动构建和训练集一致的特征矩阵
如果你不想用model.matrix,可以手动给测试矩阵加截距项,并且保证特征列的顺序和训练时的x完全一致:
# 训练集的x除了截距,特征列是A和B,所以测试集先取A、B列,再加截距 x.test = cbind(1, as.matrix(testframe[, c("A", "B")])) # 给列命名,和训练集的x保持一致 colnames(x.test) = colnames(x)
不过这种方法需要你手动对齐列名和顺序,不如第一种方法省心,尤其是特征多的时候容易出错。
总结一下,最推荐的是方式一,既规范又不容易出错,不需要额外加无用的列。
内容的提问来源于stack exchange,提问作者ahanf

