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