第6章 正则化:文本回归
线性回归的非线性扩展:广义加性模型(GAM, Generalized Additive Model)R语言中可用gam()函数实现
多项式回归:degree值不能无限增大,否则会产生过拟合(overfitting)现象。
过拟合:指模型拟合了部分噪声
避免过拟合的手段:交叉验证(cross-validation)和正则化(regularization)
模型复杂度:一个模型中特征的权重越大,这个模型越复杂
L1正则化(L1 norm)与L2正则化(L2 norm):分别累加模型系数的绝对值和系数的平方
set.seed(1) x <- seq(0, 1, by = 0.01) y <- sin(2 * pi * x) + rnorm(length(x), 0, 0.1) #L1 norm and L2 norm lm.fit <- lm(y ~ x) l2.model.complexity <- sum(coef(lm.fit) ^ 2) l1.model.complexity <- sum(abs(coef(lm.fit)))
可以训练正则化的线性模型的函数:glmnet()
x <- as.matrix(cbind(x, rev(x))) library(glmnet) glmnet(x, y)
结果解释:
Df:指明模型中的非零权重有几个(不包括截距项)
%Dev:模型的R2值。第2行对应的是完全正则化,最后一行[55]对应的是完全没有正则化
Lambda:超参数(hyperparameter)可以看做是一个惩罚参数,lambda很大时,表明对复杂度很在意,使模型权重趋于0,反之则会得到一个比较复杂的模型
为了得到最优模型,要设定一个大小适中的lambda. 为了得到这个lambda,首先设定一个较大的次数,比如10,然后使用不同的lambda分别在测试集上训练模型,再看效果如何,最后通过迭代多次不同的lambda,找到这个最好的lambda
########################################################
#文本回归
#研究描述文本与销量之间的关系
ranks <- read.csv('ML_for_Hackers/06-Regularization/data/oreilly.csv') library(tm) documents <- data.frame(Text = ranks$Long.Desc.) row.names(documents) <- 1:nrow(documents)
#将原始数据集转化为一个文档词项矩阵
#这里注意,由于tm包更新到0.6-0以后,tolower()等函数的返回有可能不是dtm的格式(之前版本默认返回相应格式)
#因此书中模型会有一个错误
#需要将代码修改为
corpus <- Corpus(DataframeSource(documents)) corpus <- tm_map(corpus, content_transformer(tolower)) corpus <- tm_map(corpus, content_transformer(stripWhitespace)) corpus <- tm_map(corpus, removeWords, stopwords('english')) dtm <- DocumentTermMatrix(corpus)
#将文档词项矩阵转换为一个简单矩阵,将顺序值颠倒过来作为预测值(使权重未正)
#初始化随机种子
#对于6个lambda值,分别交叉验证50次,并作图
x <- as.matrix(dtm) y <- rev(1:100) set.seed(1) performance <- data.frame() for(lambda in c(0.1, 0.25, 0.5, 1, 2, 5)) { for(i in 1:50) { indices <- sample(1:100, 80) training.x <- x[indices, ] training.y <- y[indices] test.x <- x[-indices, ] test.y <- y[-indices] glm.fit <- glmnet(training.x, training.y) predicted.y <- predict(glm.fit, test.x, s = lambda) rmse <- sqrt(mean((predicted.y - test.y) ^ 2)) performance <- rbind(performance, data.frame(Lambda = lambda, Iteration = i, RMSE = rmse)) } } ggplot(performance, aes(x = Lambda, y = RMSE)) + stat_summary(fun.data = 'mean_cl_boot', geom = 'errorbar') + stat_summary(fun.data = 'mean_cl_boot', geom = 'point')
分析:由图可见,随着lambda越来越大,模型的表现越来越好,但是lambda增大后是趋于简化模型,即常数模型的情况,没有用到特征的信息。
简而言之,这个文本回归模型没有发现有意义的信息,给出的预测完全是随机噪声。
#########################################################################
数据中也许并没有答案。有一堆数据和对答案的热切渴望,并不能确保真的能从这堆数据中提取出合理的预期答案。
——John Tukey
#########################################################################
将回归问题转化为分类问题,采用逻辑回归的办法再进行尝试
#分类办法:用逻辑值1/0作为分类标准,是否进入前50
#评价模型的标准用错误率评价,更多的循环次数会对错误率有更准确的估计
#代码优化:将两个循环交换,不必为每一个lambda做多次拆分,提高运行效率
set.seed(1) performance <- data.frame() for(i in 1:250) { indices <- sample(1:100, 80) training.x <- x[indices, ] training.y <- y[indices] test.x <- x[-indices, ] test.y <- y[-indices] for(lambda in c(0.0001, 0.001, 0.0025, 0.01, 0.025, 0.5, 0.1)) { glm.fit <- glmnet(training.x, training.y, family = 'binomial') predicted.y <- ifelse(predict(glm.fit, test.x, s = lambda) > 0, 1, 0) error.rate <- mean(predicted.y != test.y) performance <- rbind(performance, data.frame(Lambda = lambda, Iteration = i, ErrorRate = error.rate)) } } ggplot(performance, aes(x = Lambda, y = ErrorRate)) + stat_summary(fun.data = 'mean_cl_boot', geom = 'errorbar') + stat_summary(fun.data = 'mean_cl_boot', geom = 'point') + scale_x_log10()
结果可以看到,将回归问题改成分类问题时,较小的lambda可以预测一本书能否进入销售榜前50,是有意义的。
总结:有些情况下,手中的数据并不能解决较复杂的问题(回归问题:预测排行),但是却可以解决一些简单问题(分类问题:是否进入前50)
参考:http://stackoverflow.com/questions/24191728/documenttermmatrix-error-on-corpus-argument