---
subtitle: "TMA4268 Statistical Learning V2018"
title: "Compulsory exercise 3, tentative solutions"
author: "Mette Langaas"
date: "`r format(Sys.time(), '%d %B, %Y')`"
output: #3rd letter intentation hierarchy
  # pdf_document:
   html_document:
    toc: true
    toc_float: true
bibliography: ref.bib
nocite: |
  @ESL, @ISL, @MASS
#editor_options: 
#  chunk_output_type: console
---
  
```{r setup, include=FALSE}
knitr::opts_chunk$set(echo = TRUE,results="hold",message = FALSE,warning=FALSE)
```

# Problem 1 - Classification with trees [4 points]

```{r,echo=FALSE,eval=TRUE}
library(caret) 
#read data, divide into train and test
germancredit = read.table("http://archive.ics.uci.edu/ml/machine-learning-databases/statlog/german/german.data")
colnames(germancredit) = c("checkaccount", "duration", "credithistory", "purpose", "amount", "saving", "presentjob", "installmentrate", "sexstatus", "otherdebtor", "resident", "property", "age", "otherinstall", "housing", "ncredits", "job", "npeople", "telephone", "foreign", "response")
germancredit$response = as.factor(germancredit$response) #2=bad
#table(germancredit$response)
#str(germancredit) # to see factors and integers, numerics

set.seed(4268) #keep this -easier to grade work
in.train <- createDataPartition(germancredit$response, p=0.75, list=FALSE)
# 75% for training, one split
germancredit.train <- germancredit[in.train,]; #dim(germancredit.train)
germancredit.test <- germancredit[-in.train,];#dim(germancredit.test)
```

## a) Full classification tree [1 point]

* Q1. Explain briefly how `fulltree` is constructed. The explanation should include the words: greedy, binary, deviance, root, leaves.

**A**: Classification trees are drawn upside down, where the top node is called the root. The leaf nodes are the nodes at the bottom, with no splitting criteria. These represent the final predicted class. In addition we have internal nodes and branches between nodes.

A classification tree is built using binary splits, and a cost function is used. Here we have used the _deviance_ (for the tree) defined as $-2 \sum_{j}\sum_{k}n_{jk}log(\hat{p}_{jk})$ according to @MASS page 255 (the sum is over all nodes and all classes). Here $n_{jk}$ is the number of observations of class $k$ in node $j$ and $\hat{p}_{jk}$ is the proportion of the training data at node $j$ from class $k$, so $n_{jk}/N_{j}$ when $N_j$ is the number of training data at node $j$. (This criterion is very similar to the cross entropy, where $n_{jk}$ is replace with $\hat{p}_{jk}$.)

At each node a possible split is suggested, and the split giving the smallest deviance is chosen, given that some stopping criteria is not reached. This approach is called _greedy_ which means than future splits are not considered - only the present split. Is might be that another split (not locally the best) would give tree that globally had a lower deviance.

The leaf nodes can be seen as non-overlapping regions of the covariate space, and for each region the same classification is done (same with regression- the same prediction in each region).

For later: there are 15 terminal nodes, misclassification rate on test set is 0.244 and AUC 0.7446.

```{r,echo=TRUE,eval=TRUE}
# construct full tree
library(tree)
library(pROC)
fulltree=tree(response~.,germancredit.train,split="deviance")
summary(fulltree)
plot(fulltree)
text(fulltree)
print(fulltree)
fullpred=predict(fulltree,germancredit.test,type="class")
testres=confusionMatrix(data=fullpred,reference=germancredit.test$response)
print(testres)
1-sum(diag(testres$table))/(sum(testres$table))
predfulltree = predict(fulltree,germancredit.test, type = "vector")
testfullroc=roc(germancredit.test$response == "2", predfulltree[,2])
auc(testfullroc)
plot(testfullroc)
```



## b) Pruned classification tree [1 point]

* Q2. Why do we want to prune the full tree? 

**A**: 

* Incrase interpretation, 
* if the tree is too deep we might have overfitted the training data and pruning the tree may make this better,
* In addition pruning may avoid unnecessary splits. 

* Q3. How is amount of pruning decided in the code? 

**A**: By 5 fold cross-validation and misclassification is used as criterion. 

* Q4. Compare the the full and pruned tree classification method with focus on interpretability and the ROC curves.

**A**: 

* Interpretability: The pruned tree only have 4 terminal notes and is much easier to interpret that the full tree. This is in general the case.
* But, the `fullcv`plot is irradic and not clear to choose 4 terminal nodes. 
* The misclassification rate on the test set is 0.26 and AUC 0.7171. This is worse than for the full tree. But the difference is not big. And, the pruned tree is better for some parts of the ROC curve (see plot below).
* If we want to emphasize interpretability we should use the pruned tree, if we want to emphasize prediction accuracy (for all values of the probability cut-off) we should choose the full tree.


```{r, echo=TRUE, eval=TRUE}
# prune the full tree
set.seed(4268)
fullcv=cv.tree(fulltree,FUN=prune.misclass,K=5)
plot(fullcv$size,fullcv$dev,type="b", xlab="Terminal nodes",ylab="misclassifications")
print(fullcv)
prunesize=fullcv$size[which.min(fullcv$dev)]
prunetree=prune.misclass(fulltree,best=prunesize) 
print(prunetree)
plot(prunetree)
text(prunetree,pretty=1)
predprunetree = predict(prunetree,germancredit.test, type = "class")
prunetest=confusionMatrix(data=predprunetree,reference=germancredit.test$response)
print(prunetest)
1-sum(diag(prunetest$table))/(sum(prunetest$table))
predprunetree = predict(prunetree,germancredit.test, type = "vector")
testpruneroc=roc(germancredit.test$response == "2", predprunetree[,2])
auc(testpruneroc)
plot(testfullroc,lty=1)
plot(testpruneroc,add=TRUE,lty=2)
```



## c) Bagged trees [1 point]

* Q5. What is the main motivation behind bagging?

**A**: Bagging is short for bootstrap aggretation. The main motivation is to decrease variance (from only using one tree) by constructing many trees and taking the average of all the trees, since the variance of a mean of predictions is smaller than the variance of one prediction when the predicitions are independent or somewhat correlated (if the predictions are not perfectly correlated). This will in most cases give better performance on the expected test MSE (than only using one tree) and therefore a slightly larger bias on a test set - but a smaller variance (as compared to one tree). There is no need for pruning.

* Q6. Explain what the variable importance plots show, and give your interpretation for the data set.

**A**: 

* The variable importance plot shows the relative importance of the different covariates in the bagged tree classifier, such that the variables presented on top are more important than those below.
* The importance is defined using some measure of accuracy. In our case the Gini index (node impurity) or the accuracy directly.
* Two different strategies can be taken, but in the core we look at the mean decrease in impurity due to splits over a given predictor - averaged over all B trees. 
* Often the OBB (out-of-bag) samples are used. These are the observations that were not in the bootstrap sample when a tree was fitted.
* In our plot `checkacount`, `amount`, `duration` and `age` are ranked as important covariates (both strategies).

* Q7. Compare the performance of bagging with the best of the full and pruned tree model above with focus on interpretability and the ROC curves (AUC).

**A**: 

* Interpretability of bagged classifier:is much more difficult to see than one tree. 
* Misclassification rate on test set is now 0.196 and AUC 0.821. So, the bagged trees are clearly better at prediction than the one full tree.
* You have to choose between interpretability or prediction accuracy!

Remark: you need to know how to construct a ROC and that the AUC should be high if the method is good- and that an AUC of 0.5 is achieved by random guessing in the 2 class situation.


```{r,echo=TRUE,eval=TRUE}
library(randomForest)
set.seed(4268)
bag=randomForest(response~., data=germancredit,subset=in.train,
                 mtry=20,ntree=500,importance=TRUE)
bag$confusion
1-sum(diag(bag$confusion))/sum(bag$confusion[1:2,1:2])
yhat.bag=predict(bag,newdata=germancredit.test)
misclass.bag=confusionMatrix(yhat.bag,germancredit.test$response)
print(misclass.bag)
1-sum(diag(misclass.bag$table))/(sum(misclass.bag$table))
predbag = predict(bag,germancredit.test, type = "prob")
testbagroc=roc(germancredit.test$response == "2", predbag[,2])
auc(testbagroc)
plot(testbagroc)
varImpPlot(bag,pch=20)
```


## d) Random forest [1 point]

* Q8. The parameter `mtry=4` is used. What does this parameter mean, and what is the motivation behind choosing exactly this value?

**A**: `mtry` is the number of covariates to choose from at each binary split. These `mtry` are chosen at random at each (internal) node where a split is investigated. For classification empirical investigations have shown that `mtry` equal to the square root of the number of covariates is a good choice. Here we have 20 covariates and `sqrt(20)`=`r sqrt(20)` - giving `mtry=4` as a good choice

* Q9. The value of the parameter `mtry` is the only difference between bagging and random forest. What is the effect of choosing `mtry` to be a value less than the number of covariates?

**A**: The idea here is that the performance of the random forest will be better than the performance of the bagged treed if the trees that make up the forest are rather different -- and variance wise we know that the sum of variances for two independent predictors are smaller than for two dependent predictors ($\text{Var}(X+Y)=\text{Var}(X)+\text{Var}(Y)+2\text{Cov}(X,Y)$).
We achieve less correlated trees by not allowing each split to be made based on all possible covariates.

* Q10. Would you prefer to use bagging or random forest to classify the credit risk data?

**A**: With respect to the misclassification rate on the test set, random forest has 0.208 compared to 0.196 for bagging, but the AUC for random forest is 0.8495 which is better han 0.821 for bagging. Thus the performance of the two does not differ that much. I might be partial to the random forest here, since the trees here are less correlated than for bagging. But, the difference between the two methods is not that big. See plot with all three ROC-curves together.

```{r,echo=TRUE,eval=TRUE}
set.seed(4268)
rf=randomForest(response~.,
                 data=germancredit,subset=in.train,
                 mtry=4,ntree=500,importance=TRUE)
rf$confusion
1-sum(diag(rf$confusion))/sum(rf$confusion[1:2,1:2])
yhat.rf=predict(rf,newdata=germancredit.test)
misclass.rf=confusionMatrix(yhat.rf,germancredit.test$response)
print(misclass.rf)
1-sum(diag(misclass.rf$table))/(sum(misclass.rf$table))
predrf = predict(rf,germancredit.test, type = "prob")
testrfroc=roc(germancredit.test$response == "2", predrf[,2])
auc(testrfroc)
plot(testfullroc,lty=1)
plot(testpruneroc,add=TRUE,lty=2)
plot(testbagroc,add=TRUE,lty=3)
plot(testrfroc,add=TRUE,lty=4)
varImpPlot(rf,pch=20)
```


# Problem 2 - Nonlinear class boundaries and support vector machine [2 points]

## a) Bayes decision boundary [1 point]


```{r,echo=TRUE, eval=TRUE}
load(url("https://web.stanford.edu/~hastie/ElemStatLearn/datasets/ESL.mixture.rda"))
#names(ESL.mixture) 
#prob gives probabilites for each class when the true density functions are known
#px1 and px2 are coordinates in x1 (length 69) and x2 (length 99) where class probabilites are calculated
rm(x,y)
attach(ESL.mixture)
dat=data.frame(y=factor(y),x)
xgrid=expand.grid(X1=px1,X2=px2)
par(pty="s")
plot(xgrid, pch=20,cex=.2)
points(x,col=y+1,pch=20)
contour(px1,px2,matrix(prob,69,99),level=0.5,add=TRUE,col="blue",lwd=2) #optimal boundary
```

* Q11. What is a Bayes classifier, Bayes decision boundary and Bayes error rate? Hint: pages 37-39 in @ISL.

**A**: Bayes classifier: classify to the most probable class (the class with the highest posterior probability) gives the minimize the expected 0/1 loss. This requires that we know the posterior probability, which we usually don't know - except for situations like this (we have simulated data ourselves). The Bayes optimal boundary is the boundary for the Bayes classifier and the error rate (on a future test set drawn from the distribution of the classes) for the Bayes classifier is the Bayes error rate. The Bayes error rate is related to the irreducible error (but bias-variance decomposition is for quadratic loss).

* Q12. When the Bayes decision boundary is known, do we then need a test set?

**A**: We could of cause now base the evaluation on only comparing class boundaries, this is really all we need. We might also have access to the Bayes error rate - and then we know what the error rate on a future test set would be. But, it is also useful to know where we expect to get test data (they will probably not arrive at a grid) and comparing misclassificaton rates for two or more methods is a nice numerical comparision.  

## b) Support vector machine [1 point]

* Q13. What is the difference between a support vector classifier (SVC) and a support vector machine (SVM)?

**A**: SVC: Linear method with linear class boundaries. SVM: general kernels gives non-linear class boundaries.
Can be written as optimization problems, the only difference is in choice of kernels.

###SVC
$$\mathrm{maximize}_{\beta_0,\beta_1,...,\beta_p,\epsilon_1,...,\epsilon_n,} \quad M \text{ subject to} \sum_{j=1}^p \beta_j^2=1$$
$$y_i(\beta_0+\beta_1 x_{i1}+\beta_2 x_{i2}+...+\beta_p x_{ip})\geq M(1-\epsilon_i) \quad  \forall i=1,...,n.$$
$$\epsilon_i\geq 0, \quad \sum_{i=1}^n \epsilon_i \leq C.$$

* $M$ is the width of the margin.
* $\epsilon_1,...,\epsilon_n$ are _slack variables_. 
     + If $\epsilon_i=0$ it means that observation $i$ is on the correct side of the margin, 
     + if $\epsilon_i>0$ observation $i$ is on the wrong side of the margin, and 
     + if $\epsilon_i>1$ observation $i$ is on the wrong side of the hyperplane. 
* $C$ is a _tuning (regularization) parameter_ (chosen by cross-validation) giving the _budget for slacks_. It restricts the number of the training observations that can be on the wrong side of the hyperplane. No more than $C$ of the observations can be on the wrong side. 


###SVM
$$\mathrm{maximize}_{\beta_0,\alpha_1,...,\alpha_n,\epsilon_1,...,\epsilon_n,} \quad M $$
$$y_i(f({\bf x}_i))\geq M(1-\epsilon_i) \quad  \forall i=1,...,n.$$
$$\epsilon_i\geq 0, \quad \sum_{i=1}^n \epsilon_i \leq C.$$

where 
$$f({\bf x})=\beta_0 +\sum_{i\in \cal{S}} \alpha_i K({\bf x},{\bf x}_i)$$

* Q14. What are parameters for the support vector classifier and the support vector machine? How are these chosen above?

**A**: For both: cost. NB the cost and $C$ is not the same. Parameter: SVM also width of radial bases. Chosen by 10 fold cross validation. 
The cost is related to the amount of violations we may accept (on the wrong side of the margin). The width of the radial bases is related to the wigglyness of the solution. The width is related to the standard deviation in the multivariate normal distribution, and if the standard deviation is small we would a more wiggly curve than a large standard deviation.

* Q15. How would you evaluate the support vector machine decision boundary compared to the Bayes decision boundary?

**A**: Seems like we have overfitted the training data? The boudary is very similar to the Bayes classifier for areas with much data, but rather different where we do not have so much data.


```{r,echo=TRUE,eval=TRUE}
library(e1071)
# support vector classifier
svcfits=tune(svm,factor(y)~.,data=dat,scale=FALSE,kernel="linear",ranges=list(cost=c(1e-2,1e-1,1,5,10)))
summary(svcfits)
svcfit=svm(factor(y)~.,data=dat,scale=FALSE,kernel="linear",cost=0.01)
# support vector machine with radial kernel
svmfits=tune(svm,factor(y)~.,data=dat,scale=FALSE,kernel="radial",ranges=list(cost=c(1e-2,1e-1,1,5,10),gamma=c(0.01,1,5,10)))
summary(svmfits)
svmfit=svm(factor(y)~.,data=dat,scale=FALSE,kernel="radial",cost=1,gamma=5)

# the same as in a - the Bayes boundary
par(pty="s")
plot(xgrid, pch=20,cex=.2)
points(x,col=y+1,pch=20)
contour(px1,px2,matrix(prob,69,99),level=0.5,add=TRUE,col="blue",lwd=2) #optimal boundary

# decision boundaries from svc and svm added
svcfunc=predict(svcfit,xgrid,decision.values=TRUE)
svcfunc=attributes(svcfunc)$decision
contour(px1,px2,matrix(svcfunc,69,99),level=0,add=TRUE,col="red") #svc boundary
svmfunc=predict(svmfit,xgrid,decision.values=TRUE)
svmfunc=attributes(svmfunc)$decision
contour(px1,px2,matrix(svmfunc,69,99),level=0,add=TRUE,col="orange") #svm boundary
```


# Problem 3 - Unsupervised methods [2 points]

## a) Principal component analysis [1 point]

* Q16. Explain what you see in the `biplot` in relation to the loadings for the first two principal components. 

**A**: Read off on horisontal and vertical axes values for loadings. This is a bit strange at first - so give example. Looking at position of the words (coffee, cocoa, ...) - not the tip of the red arrows-  and PC1 on _top_ of plot: we see that this is constructed from around -0.25 times coffe, -0.5 times wine, 0.2 times cocoa, 0.4 times beer and 0.65 times tea and approx 0 for liquer. Compare to loadings this makes sense but easier to look at numbers than position of words in plot with double axes values.
A bit too much info in the plot for me - prefer scores alone and then read off loadings from matrix. 

The loadings for the first PC gives the direction in the space of the scaled covariates where the variance is largest, the second PC direction is orthogonal to the first and the variance is the second largest.

Why was the correlation plot included? Just to see that there are correlations in the data - if only independence no need for PCA on scale variables. Would then only give original variables.

* Q17. Does this analysis give you any insight into the consumption of beverages and similarities between countries? 

**A**: See which countries are place together in PC1-PC2 plot. For each country the PC1 score is the value for the country in the PC1 direction (the linear combination of the beverages consumptions weighted by the loadings), ditto for PC2. Not surprising that Great Br and Ireland look similar with high consumption of tea. 

```{r,echo=TRUE,eval=TRUE}
# reading data on consumption of different beverages for countries
drink <- read.csv("https://www.math.ntnu.no/emner/TMA4267/2017v/drikke.TXT",sep=",",header=TRUE)
drink <- na.omit(drink)
# looking at correlation between consumptions
drinkcorr=cor(drink)
library(corrplot)
corrplot(drinkcorr,method="circle")
# now for PCA
pcaS <- prcomp(drink,scale=TRUE) # scale: variables are scaled 
pcaS$rotation
summary(pcaS)
biplot(pcaS,scale=0,cex=0.6) # scale=0: arrows scaled to represent the loadings

# nb ta inn også uten skalering

```


## b) Hierarchical clustering [1 point]

```{r,echo=TRUE, eval=TRUE}
library(knitr)
species=c("Human","Chimpanzee","Gorilla","Orangutan","Gibbon")
distJC <- matrix(c(0,1,3,9,12,
                   1,0,2,8,11,
                   3,2,0,6,11,
                   9,8,6,0,11,
                   12,11,11,11,0),5,5)
dimnames(distJC) <- list(species,species)
kable(distJC)

# code used to produce the plots
#png("speciesclust.png")
par(mfrow=c(1,3))

obj <- hclust(as.dist(distJC),"complete")
plot(obj,xlab="",main="A",sub="",cex=1.2)
axis(side=2,at=obj$height)

obj <- hclust(as.dist(distJC),"average")
plot(obj,xlab="",main="B",sub="",cex=1.2)
axis(side=2,at=round(obj$height,1))

obj <- hclust(as.dist(distJC),"single")
plot(obj,xlab="",main="C",sub="",cex=1.2)
axis(side=2,at=obj$height)
#dev.off()

```

#![](speciesclust.png)


* Q18. Describe how the distance between _clusters_ are defined for single, complete and average linkage. 

**A**

* Single linkage: the distance between two clusters is the smallest distance between an object in one cluster and an object in the other cluster.
* Complete linkage: the distance between two clustes is the largest distance between an object in one cluster and an object in the other cluster.
* Average linkage: the distance between two clusters is the average distance between all possible pairs consisting of one object from one cluster and one object from the other cluster. 

* Q19. Identify which of the three dendrograms (A, B, C) correspond to the three methods single, complete and average linkage. Justify your solution.

**A**: The smallest distance between any species is between human and chimpanzee, so these are fused together, and then new distance matrices are make for single, complete and average linkage. These are:

```{r}
distJC <- matrix(c(0,1,3,9,12,
                   1,0,2,8,11,
                   3,2,0,6,11,
                   9,8,6,0,11,
                   12,11,11,11,0),5,5)
dimnames(distJC) <- list(species,species)
mspecies=c("humchimp",species[3:5])
singlemat= matrix(c(0,2,8,11,2,0,6,11,8,6,0,11,11,11,11,0),4,4)
completemat= matrix(c(0,3,9,12,3,0,6,11,9,6,0,11,12,11,11,0),4,4)
averagemat= matrix(c(0,2.5,8.5,11.5,2.5,0,6,11,8.5,6,0,11,11.5,11,11,0),4,4)
dimnames(singlemat)=dimnames(completemat)=dimnames(averagemat)=list(mspecies,mspecies)
kable(singlemat)
kable(completemat)
kable(averagemat)
```

The smallest distance is now between "humchimp" and gorilla. This distance is 
* single linkage: 2 - so this must be figure C
* complete linkgage: 3 - so this must be figure A
* average linkage: 2.5 - so this must be figure B.


# Problem 4 - Neural networks [2 points]

* Q20. What is the advantage of using a non-linear activation function such as `relu`?


**A** If only a linear activation function is used, the output of the neural network will be a linear function of the input. Hence, a non-linear function might add to the model complexity - and provide a more flexible solution.

* Q21. Why do we need to use a different activation function (`sigmoid`) in the output layer instead of using `relu` again? 

**A** The output of the final layer shall represent the probability of the sample belonging to one of the classes. In order to get results which can be interpreted as such (number between 0 and 1), we can use `sigmoid` as the activation function in the final layer. This is the same as when we used the logistic function (other name for sigmoid) in logistic regression.

```{r,echo=TRUE,eval=TRUE,cache=TRUE}
library(keras)
library(ggplot2)
imdb <- dataset_imdb(num_words = 10000)
train_data <- imdb$train$x
train_labels <- imdb$train$y
test_data <- imdb$test$x
test_labels <- imdb$test$y
vectorize_sequences <- function(sequences, dimension = 10000) {
  results <- matrix(0, nrow = length(sequences), ncol = dimension)
  for (i in 1:length(sequences))
    results[i, sequences[[i]]] <- 1                                     
  results
}
val_indices <- 1:10000
x_train <- vectorize_sequences(train_data)
x_test <- vectorize_sequences(test_data)
y_train <- as.numeric(train_labels)
y_test <- as.numeric(test_labels)
y_val <- y_train[val_indices]
partial_y_train <- y_train[-val_indices]
```

```{r,echo=TRUE,eval=TRUE,cache=TRUE}
library(keras)
library(ggplot2)
model_complex <- keras_model_sequential() %>%
  layer_dense(units = 32, activation = "relu", input_shape = c(10000)) %>%
  layer_dense(units = 32, activation = "relu") %>%
  layer_dense(units = 1, activation = "sigmoid")
model_complex %>% compile(
  optimizer = "rmsprop",
  loss = "binary_crossentropy",
  metrics = c("accuracy")
)
val_indices <- 1:10000

x_val <- x_train[val_indices,]
partial_x_train <- x_train[-val_indices,]
y_val <- y_train[val_indices]
partial_y_train <- y_train[-val_indices]
history_complex <- model_complex %>% fit(
  partial_x_train,
  partial_y_train,
  epochs = 20,
  batch_size = 512,
  validation_data = list(x_val, y_val)
)
plot(history_complex)+ggtitle("Complex model")

# so to the simple model with 4 units in each layer
model_simple <- keras_model_sequential() %>%
  layer_dense(units = 4, activation = "relu", input_shape = c(10000)) %>%
  layer_dense(units = 4, activation = "relu") %>%
  layer_dense(units = 1, activation = "sigmoid")
model_simple %>% compile(
  optimizer = "rmsprop",
  loss = "binary_crossentropy",
  metrics = c("accuracy")
)
history_simple<- model_simple %>% fit(
  partial_x_train,
  partial_y_train,
  epochs = 20,
  batch_size = 512,
  validation_data = list(x_val, y_val)
)
plot(history_simple)+ggtitle("Simple model")
```

* Q22. Plot the training and validation loss and accuracy for the simpler and more complex model mentioned above. How do they compare with the model with 16 hidden units?

**A**: Simple - seems to fit better than the 16-node solution, but also here we might be overfitting if we do not stop early. This network has 40001 weights from input to the first layer, and then 20 (4+4*4) from first hidden to second hidden, and then 5 from the second to the output.
The more complex newwork - not so different from the 16-node solution in the text - so overfitting if not early stopping (or other strategy). This network has $32*10000+1$ weightd from the input to the first hidden layer, then $32+32*32$ from first to second hidden layer, and $1+32$ from second hidden to output. 

* Q23. Besides reducing the network's size, what other methods can be used to avoid overfitting with neural network models? Briefly describe the intuition behind each one.

**A** In order to avoid overfitting, we can: (see file 7 NN)

* Include more data, providing more accurate esimates of the coefficients.
* Add weight regularization, this is: Penalize the loss function with the magnitude of the coefficients. This makes the model less flexible, which prevents overfitting. Both ridge and lasso type regularization is available in `keras` and also the combination thereof (referred to as elastic net in statistics).
* Include dropout: Randomly remove input to a layer during training. This seeks to induse noise to the data in order to break up patterns occuring by chance, but is difficult to "prove" statistically.
    

# References

