blob: 1fce86cddc7fdf6374941207c8c7c16545672f6e [file] [log] [blame]
PeterFankhauserIDS41425dc2021-02-23 13:09:57 +01001library(caret)
2library(tidyverse)
3library(DMwR)
4library(randomForest)
5library(FSelector)
6# library(randomForestExplainer)
7# may need to: options(expressions = 5e5) to avoid stackoverflow for installing package
8
9set.seed(42)
10
11ngramfilegold<-"goldstandard01_anno_ml_synfeat_nstop1"
12ngramfiletest<-"wikilist_cleanup_syn"
13setwd(dirname(rstudioapi::getSourceEditorContext()$path))
14stopwords <- readLines(con = "../data/stopwords.txt",encoding="UTF-8")
15oringramme <- read.csv(paste("../data/",ngramfilegold,".csv",sep=""), header = TRUE, sep = "\t", dec=".", quote="", encoding="UTF-8",stringsAsFactors=FALSE)
16oringrammetest <- read.csv(paste("../data/",ngramfiletest,".csv",sep=""), header = TRUE, sep = "\t", dec=".", quote="", encoding="UTF-8",stringsAsFactors=FALSE)
17syfeaturenames <- read.csv("../data/syfeatures.tsv", header = TRUE, sep = "\t", dec=".", quote="", encoding="UTF-8",stringsAsFactors=FALSE)
18# syfeaturenames$navalue<-sapply(syfeaturenames$navalue,as.numeric)
19
20deleteStopwords = function(wl, stopwords = NULL) {
21 wl[!(wl %in% stopwords)]
22}
23
24# featuresets
25
26covars <- c("CO_LL", "CO_Z", "CO_G", "CO_T", "CO_LOGDICE", "CO_PMI", "CO_MI3", "CO_DEREKO", "CO_SGT", "CO_WIN5_VEC","CO_WIN5_VEC_AUTOSEM")
27syvars <- c(syfeaturenames$synames,"NSTOPW")
28vars <- c(covars,syvars)
29
30oringramme <- oringramme %>%
31 mutate(CO_IDIOM = ifelse(is.na(CO_IDIOM),0,CO_IDIOM)) %>%
32 filter(CO_IDIOM < 2) # just two classes: 0 no idiom, 1 idiom
33
34# Reduce number of classes, treat null values, add NSTOPW, change names for SY features
35
36ngramme <- oringramme %>%
37 add_column(NSTOPW = sapply(oringramme$tokens,function(x) length(deleteStopwords(tolower(unlist(strsplit(x," "))),stopwords)))) %>%
38 add_column(nstokens = sapply(oringramme$tokens, function(x) paste(deleteStopwords(tolower(unlist(strsplit(x," "))),stopwords),collapse=" "))) %>%
39 # select(-matches("CO_TOKEN.*"), -tokens) %>%
40 select(-matches("CO_TOKEN.*")) %>% # keep tokens for interpretability
41 mutate(across(matches(".rank.*"), ~ replace_na(.x, 1000))) %>%
42 mutate(across(c("dice", "lfmd", "llr", "ld", "pmi"), ~ replace_na(.x, min(.x) - 1))) %>%
43 rename_at(syfeaturenames$innames, ~ syfeaturenames[syfeaturenames$innames==.x,]$synames ) %>%
44 mutate(across(everything(), ~ replace_na(.x, 0))) %>%
45 mutate(CO_IDIOM = as.factor(if_else(CO_IDIOM == 1, "idiom", "no_idiom"))) %>% # just two classes: 0 no idiom, 1 idiom
46 add_column(GOLD=1) %>%
47 select(c("CO_IDIOM","tokens","nstokens","GOLD",all_of(syvars)))
48
49# discard all ngrams with less than 2 non stopwords (no syntagmatic features possible)
50
51ngramme <- ngramme %>%
52 filter(NSTOPW > 1)
53
54# remove duplicates after stopword exclusion
55
56ngramme <- ngramme %>% distinct(nstokens,.keep_all=T)
57
58# wiki ngrams
59
60wikingramme <- oringrammetest %>%
61 add_column(NSTOPW = sapply(oringrammetest$tokens,function(x) length(deleteStopwords(tolower(unlist(strsplit(x," "))),stopwords)))) %>%
62 add_column(nstokens = sapply(oringrammetest$tokens, function(x) paste(deleteStopwords(tolower(unlist(strsplit(x," "))),stopwords),collapse=" "))) %>%
63 # select(-matches("CO_TOKEN.*"), -tokens) %>%
64 select(-c(IDIOM,KERN)) %>% # keep tokens for interpretability
65 mutate(across(matches(".rank.*"), ~ replace_na(.x, 1000))) %>%
66 mutate(across(c("dice", "lfmd", "llr", "ld", "pmi"), ~ replace_na(.x, min(.x) - 1))) %>%
67 rename_at(syfeaturenames$innames, ~ syfeaturenames[syfeaturenames$innames==.x,]$synames ) %>%
68 mutate(across(everything(), ~ replace_na(.x, 0))) %>%
69 add_column(CO_IDIOM = as.factor("idiom")) %>%
70 add_column(GOLD = 0) %>%
71 select(c("CO_IDIOM","tokens","nstokens","GOLD",all_of(syvars)))
72
73# remove all with NSTOPW > 1 (no syntagmatic context available)
74
75wikingramme <- wikingramme %>%
76 filter(NSTOPW > 1)
77
78# remove duplicates after stopword exclusion
79
80wikingramme <- wikingramme %>% distinct(nstokens,.keep_all=T)
81
82# find duplicates by lower cased tokens without stopwords
83
84bothngramme <- merge(ngramme[,c("tokens","nstokens","CO_IDIOM")],wikingramme[,c("tokens","nstokens","CO_IDIOM")],by="nstokens")
85
86# 100% agreement ;)
87
88# combine
89
90allngramme <- rbind(ngramme,wikingramme)
91
92
93# and again remove duplicates
94
95allngramme <- allngramme %>% distinct(nstokens,.keep_all=T)
96
97ngramme <- allngramme %>% filter(GOLD==1)
98wikingramme <- allngramme %>% filter(GOLD==0)
99
100# formulae for training and testing rf
101
102fmla <- as.formula(paste("CO_IDIOM ~ ", paste(vars, collapse= "+")))
103fmlaco <- as.formula(paste("CO_IDIOM ~ ", paste(covars, collapse= "+")))
104fmlasy <- as.formula(paste("CO_IDIOM ~ ", paste(syvars, collapse= "+")))
105
106# Train/Test split
107
108# Training: 80% Gold Standard, no_idiom + 100% Gold Standard, idiom
109# Test: 20% Gold Standard, no_idiom + 100 % Wiki, idiom
110
111noidiomsgold <- ngramme %>% filter(CO_IDIOM=="no_idiom")
112idiomsgold <- ngramme %>% filter(CO_IDIOM=="idiom")
113
114trainRows <- sample(nrow(noidiomsgold), nrow(noidiomsgold)*0.8, replace = FALSE)
115train <- rbind(noidiomsgold[trainRows,],idiomsgold)
116test <- rbind(noidiomsgold[setdiff(1:nrow(noidiomsgold),trainRows),],wikingramme)
117
118cat("Random Forest\n")
119
120rf_classifier = randomForest(fmlasy, train, importance=TRUE)
121
122prediction_for_table <- predict(rf_classifier, test %>% select(-CO_IDIOM))
123
124res <- confusionMatrix(prediction_for_table, test$CO_IDIOM, positive= "idiom")
125print(res)
126collected_results <- bind_cols("rf" = res$byClass)
127
128# Sensitivity is recall of class 1
129# Pos Pred Value is precision
130varImpPlot(rf_classifier)
131
132cat("Random Forest with cutoff\n")
133prediction_for_table <- predict(rf_classifier,test %>% select(-CO_IDIOM), cutoff = c(0.2, 0.8))
134res <- confusionMatrix(prediction_for_table,test$CO_IDIOM, positive = "idiom")
135collected_results <- bind_cols(collected_results, "rf with cutoff" = res$byClass)
136print(res)
137
138cat("With SMOTE resampled training data\n")
139smoted.data <- SMOTE(fmlasy, subset(train, select = c("CO_IDIOM", syvars)), perc.over = 1200, perc.under = 100)
140rf_classifier = randomForest(fmlasy, smoted.data, importance=TRUE)
141prediction_for_table <- predict(rf_classifier,test %>% select(-CO_IDIOM))
142res <- confusionMatrix(prediction_for_table,test$CO_IDIOM, positive = "idiom")
143collected_results <- bind_cols(collected_results, "rf with SMOTE" = res$byClass)
144print(res)
145
146cat("With SMOTE and cutoff\n")
147prediction_for_table <- predict(rf_classifier,test %>% select(-CO_IDIOM), cutoff = c(0.2, 0.8))
148res <- confusionMatrix(prediction_for_table,test$CO_IDIOM, positive = "idiom")
149collected_results <- bind_cols(collected_results, "rf with SMOTE and cutoff" = res$byClass)
150print(res)
151
152collected_results <- collected_results %>%
153 round(3) %>%
154 add_column(measure = names(res$byClass)) %>%
155 column_to_rownames("measure")
156
157View(collected_results)
158
159# Analysing tradeoff between Fscore, Recall, Precision for various cutoffs
160# full range from precision almost 100% to recall almost 100%
161rf_classifier = randomForest(fmlasy, train, importance=TRUE)
162cvalues<-tibble()
163for (c in c(seq(from=0.4,to=0.99,by=0.025),0.999)) {
164 prediction_for_table <- predict(rf_classifier, test %>% select(-CO_IDIOM), cutoff = c(1-c, c))
165 conf<-confusionMatrix(prediction_for_table, test$CO_IDIOM, positive = "idiom")
166 cvalues <-bind_rows(cvalues, c(cutoff=c, conf$byClass))
167}
168cvalues %>%
169 select(c("cutoff", "Recall", "Precision", "F1", "Specificity", "Balanced Accuracy")) %>%
170 pivot_longer(!cutoff, names_to=c("measure")) %>%
171 ggplot(aes(cutoff, value, colour=measure)) + geom_line() +
172 scale_x_continuous(breaks = scales::pretty_breaks(n = 10)) +
173 scale_y_continuous(breaks = scales::pretty_breaks(n = 10))
174
175
176# Using estimates by random forest on entire dataset
177
178library(randomForest)
179rf_classifier_full = randomForest(fmlasy, data=allngramme, importance=TRUE)
180rf_classifier_full
181# class.error is 1 - recall
182varImpPlot(rf_classifier_full)
183
184# Feature ranking
185
186# rf features as table
187
188# correlated features seem to split their rankings
189
190rfranks<-importance(rf_classifier_full)[,3:4]
191
192# ttest
193
194idioms<-ngramme %>% filter(CO_IDIOM == "idiom")
195nonidioms<-ngramme %>% filter(CO_IDIOM != "idiom")
196
197ttestPvalues<-sapply(syvars,
198 function(sel) t.test(idioms[sel],nonidioms[sel])$p.value)
199
200# information gain
201# multiply by 1000 to avoid undersized bins
202# features are ranked individually no matter their correlation
203igain<-information.gain(fmlasy, data=ngramme%>%mutate_at(syvars, ~ . * 1000),unit="log2")
204
205featureRanks<-cbind(rfranks,igain,ttestPvalues)
206
207#randomForestExplainer::explain_forest(rf_classifier )
208
209# averate estimates and feature ranks over 10 runs
210
211errrate<-0
212conf<-matrix(0,2,3)
213featureRanks<-matrix(0,4,length(vars))
214for (i in 1:10) {
215 rfc =randomForest(fmlasy, data=ngramme, importance=TRUE)
216 #rfc =randomForest(fmlasy, data=ngramme, importance=TRUE, cutoff=c(0.2, 0.8))
217 errrate<-errrate+rfc$err.rate[100,1]
218 conf<-conf+rfc$confusion
219 featureRanks<-featureRanks+
220 cbind(importance(rfc)[,3:4],
221 information.gain(fmlasy, data=ngramme%>%mutate_at(syvars, ~ . * 1000),unit="log2"),
222 sapply(syvars,
223 function(sel) t.test(idioms[sel],nonidioms[sel])$p.value))
224 print(errrate/i)
225 conf1<-round(
226 rbind(
227 cbind(conf[,1:2]/i,(1-conf[,3]/i)*100),
228 c(100*diag(conf[,1:2])/colSums(conf[,1:2]),NA),
229 c(rowSums(conf[,1:2]/i),NA)),digits=2)
230 colnames(conf1)<-c("1","0","rec")
231 rownames(conf1)<-c("1","0","prec","sum")
232 print(conf1)
233}
234featureRanks<-featureRanks/10
235colnames(featureRanks)<-c("MeanDecreaseAccuracy","MeanDecreaseGini","InformationGain","Ttest")
236
237
238
239