Statistics, Prediction, and Reproducibility
ETHZ: 227-0966-00L
## Loading required package: knitr
David J.C. MacKay, Bayesian Interpolartion (1991) [http://citeseer.ist.psu.edu/viewdoc/summary?doi=10.1.1.27.9072]
The course has covered imaging enough and there have been a few quantitative metrics, but “big” has not really entered.
What does big mean?
So what is “big” imaging
Going back to our original cell image
We have at least a few samples (or different regions), large number of metrics and an almost as large number of parameters to tune
One of the most repeated criticisms of scientific work is that correlation and causation are confused.
There are two broad classes of data and scientific studies.
We examined 100 people and the ones with blue eyes were on average 10cm taller
In 100 cake samples, we found a 0.9 correlation between cooking time and bubble size
We examined 50 mice with gene XYZ off and 50 gene XYZ on and as the foot size increased by 10%
We increased the temperature and the number of pores in the metal increased by 10%
incremental: true
Since most of the experiments in science are usually specific, noisy, and often very complicated and are not usually good teaching examples
We normally assume
\[ P(\mathcal{F}_1(\mathcal{X})*\mathcal{F}_2(\mathcal{X}))= P(\mathcal{F}_1(\mathcal{X}))*P(\mathcal{F}_2(\mathcal{X}))\]
\[ E(\prod_{i=0}^\infty \mathcal{F}_i(\mathcal{X})) = E(\mathcal{X}) \]
Coin flips are very simple and probably difficult to match to another experiment. A very popular dataset for learning about such values beyond ‘coin-flips’ is called the Iris dataset which covers a number of measurements from different plants and the corresponding species.
Sepal.Length | Sepal.Width | Petal.Length | Petal.Width | Species | |
---|---|---|---|---|---|
35 | 4.9 | 3.1 | 1.5 | 0.2 | setosa |
41 | 5.0 | 3.5 | 1.3 | 0.3 | setosa |
57 | 6.3 | 3.3 | 4.7 | 1.6 | versicolor |
73 | 6.3 | 2.5 | 4.9 | 1.5 | versicolor |
81 | 5.5 | 2.4 | 3.8 | 1.1 | versicolor |
<img | src=“pres_figur | es/unnamed-chu | nk-3-1.png" wid | th=“640” style | =“display: block; margin: auto;” /> |
A very broad topic with plenty of sub-areas and deeper meanings. We mean two things by reproducibility
The process of going from images to numbers is detailed in a clear manner that anyone, anywhere could follow and get the exact (within some tolerance) same numbers from your samples
Everything for analysis + taking a measurement several times (noise and exact alignment vary each time) does not change the statistics significantly
The basis for reproducible scripts and analysis are scripts and macros. Since we will need to perform the same analysis many times to understand how reproducible it is.
IMAGEFILE=$1
THRESHOLD=130
matlab -r "inImage=$IMAGEFILE; threshImage=inImage>$THRESHOLD; analysisScript;"
java -jar ij.jar -macro TestMacro.ijm blobs.tif
Rscript -e "library(plyr);..."
The intraclass correlation coefficient basically looking at how similar objects within a group are compared to between groups
\[ ICC = \frac{S_A^2}{S_A^2+S_W^2} \]
where - \(S_A^2\) is the variance among groups or classes - Estimate with the standard deviations of the mean values for each group - \(S_W^2\) is the variance within groups or classes. - Estimate with the average of standard deviations for each group
We have one biased coin and try to figure out how many flips we need for the ICC to tell the difference to the normal coin
With many thousands of flips we eventually see a very strong difference but unless it is very strongly biased ICC is a poor indicator for the differences
Once the reproducibility has been measured, it is possible to compare groups. The idea is to make a test to assess the likelihood that two groups are the same given the data
We have 1 coin from a magic shop - our assumptions are - we flip and observe flips of coins accurately and independently - the coin is invariant and always has the same expected value - our null hypothesis is the coin is unbiased \(E(\mathcal{X})=0.5\) - we can calculate the likelihood of a given observation given the number of flips (p-value)
Number of Flips | Probability of All Heads Given Null Hypothesis (p-value) |
---|---|
1 | 50 % |
5 | 3.1 % |
10 | 0.1 % |
How good is good enough?
Since we do not usually know our distribution very well or have enough samples to create a sufficient probability model
We assume the distribution of our stochastic variable is normal (Gaussian) and the t-distribution provides an estimate for the mean of the underlying distribution based on few observations.
Incorporates this distribution and provides an easy method for assessing the likelihood that the two given set of observations are coming from the same underlying process (null hypothesis)
Back to the magic coin, let’s assume we are trying to publish a paper, we heard a p-value of < 0.05 (5%) was good enough. That means if we get 5 heads we are good!
Number of Flips | Probability of All Heads Given Null Hypothesis (p-value) |
---|---|
1 | 50 % |
4 | 6.2 % |
5 | 3.1 % |
Number of Friends Flipping | Probability Someone Flips 5 heads |
---|---|
1 | 3.1 % |
10 | 27.2 % |
20 | 47 % |
40 | 71.9 % |
80 | 92.1 % |
Clearly this is not the case, otherwise we could keep flipping coins or ask all of our friends to flip until we got 5 heads and publish
The p-value is only meaningful when the experiment matches what we did. - We didn’t say the chance of getting 5 heads ever was < 5% - We said if we have exactly 5 observations and all of them are heads the likelihood that a fair coin produced that result is <5%
Many methods to correct, most just involve scaling \(p\). The likelihood of a sequence of 5 heads in a row if you perform 10 flips is 5x higher.
This is very bad news for us. We have the ability to quantify all sorts of interesting metrics - cell distance to other cells - cell oblateness - cell distribution oblateness
So lets throw them all into a magical statistics algorithm and push the publish button
With our p value of less than 0.05 and a study with 10 samples in each group, how does increasing the number of variables affect our result
Using the simple correction factor (number of tests performed), we can make the significant findings constant again
So no harm done there we just add this correction factor right? Well what if we have exactly one variable with shift of 1.0 standard deviations from the other.
- Borrowed from http://peekaboo-vision.blogspot.ch/2013/01/machine-learning-cheat-sheet-for-scikit.html
Basically all of these are ultimately functions which map inputs to outputs.
The input could be
The output is
The most serious problem with machine learning and such approachs is overfitting your model to your data. Particularly as models get increasingly complex (random forest, neural networks, deep learning, …), it becomes more and more difficult to apply common sense or even understand exactly what a model is doing and why a given answer is produced.
magic_classifier = {}
# training
magic_classifier['Dog'] = 'Animal'
magic_classifier['Bob'] = 'Person'
magic_classifier['Fish'] = 'Animal'
Now use this classifier, on the training data it works really well
magic_classifier['Dog'] == 'Animal' # true, 1/1 so far!
magic_classifier['Bob'] == 'Person' # true, 2/2 still perfect!
magic_classifier['Fish'] == 'Animal' # true, 3/3, wow!
On new data it doesn’t work at all, it doesn’t even execute.
magic_classifier['Octopus'] == 'Animal' # exception?! but it was working so well
magic_classifier['Dan'] == 'Person' # exception?!
The above example appeared to be a perfect trainer for mapping names to animals or people, but it just memorized the inputs and reproduced them at the output and so didn’t actually learn anything, it just copied.
Relevant for each of the categories, but applied in a slightly different way depending on the group. The idea is two divide the dataset into groups called training and validation or ideally training, validation, and testing. The analysis is then
Here we return to the iris data set and try to automatically classify flowers
Sepal.Length | Sepal.Width | Petal.Length | Petal.Width | Species | |
---|---|---|---|---|---|
103 | 7.1 | 3.0 | 5.9 | 2.1 | virginica |
57 | 6.3 | 3.3 | 4.7 | 1.6 | versicolor |
29 | 5.2 | 3.4 | 1.4 | 0.2 | setosa |
135 | 6.1 | 2.6 | 5.6 | 1.4 | virginica |
37 | 5.5 | 3.5 | 1.3 | 0.2 | setosa |
We first decide on a split, in this case 60%, 30%, 10% for training, validation, and testing and randomly divide up the data.
div.iris<-iris %>%
mutate(
# generate a random number uniformally between 0 and 1
rand_value = runif(nrow(iris)),
# divide the data based on how high this number is into different groups
data_div = ifelse(rand_value<0.6,"Training",
ifelse(rand_value<0.9,"Validation",
"Testing")
)
) %>% select(-rand_value) # we don't need this anymore
div.iris %>% sample_n(4) %>% kable(digits=2)
Sepal.Length | Sepal.Width | Petal.Length | Petal.Width | Species | data_div | |
---|---|---|---|---|---|---|
55 | 6.5 | 2.8 | 4.6 | 1.5 | versicolor | Validation |
77 | 6.8 | 2.8 | 4.8 | 1.4 | versicolor | Training |
100 | 5.7 | 2.8 | 4.1 | 1.3 | versicolor | Validation |
127 | 6.2 | 2.8 | 4.8 | 1.8 | virginica | Training |
<img s | rc=“pres_figure | s/unnamed-chun | k-19-1.png" wid | th=“640” style | =“display: bl | ock; margin: auto;" /> |
Making a decision tree can be done by providing the output (class
) as a function of the input, in this case just a combination of x1 and y1 (~x1+y1
). From this a
library(rpart)
library(rpart.plot)
training.data <- div.iris %>% subset(data_div == "Training")
dec.tree<-rpart(Species~Sepal.Length+Sepal.Width,data=iris)
A tree can be visualized graphically as a trunk (the top most node) dividing progressively into smaller subnodes
or as a list of rules to apply
## n= 150
##
## node), split, n, loss, yval, (yprob)
## * denotes terminal node
##
## 1) root 150 100 setosa (0.33333333 0.33333333 0.33333333)
## 2) Sepal.Length< 5.45 52 7 setosa (0.86538462 0.11538462 0.01923077)
## 4) Sepal.Width>=2.8 45 1 setosa (0.97777778 0.02222222 0.00000000) *
## 5) Sepal.Width< 2.8 7 2 versicolor (0.14285714 0.71428571 0.14285714) *
## 3) Sepal.Length>=5.45 98 49 virginica (0.05102041 0.44897959 0.50000000)
## 6) Sepal.Length< 6.15 43 15 versicolor (0.11627907 0.65116279 0.23255814)
## 12) Sepal.Width>=3.1 7 2 setosa (0.71428571 0.28571429 0.00000000) *
## 13) Sepal.Width< 3.1 36 10 versicolor (0.00000000 0.72222222 0.27777778) *
## 7) Sepal.Length>=6.15 55 16 virginica (0.00000000 0.29090909 0.70909091) *
Overlaying with the prediction data looks good
It struggles more with the validation data since it has never seen it before and it’s not quite the same as the training
The test data (normally we would not look at it at all right now and wait until the very end) looks even worse and an even smaller fraction is correctly matched.
Taking a list of points (feature vectors) where each has an \(x1\) and a \(y1\) coordinate and a classification (Happy or Sad), we can show the data as a table
x1 | y1 | class | |
---|---|---|---|
131 | -30.00 | 0.00 | Sad |
24 | 8.34 | 25.68 | Happy |
28 | 15.39 | 17.09 | Happy |
26 | 12.50 | 21.65 | Happy |
57 | 4.60 | 43.76 | Sad |
Or graphically
You can play around with neural networks and this data set at TensorFlow Playground
We first decide on a split, in this case 60%, 30%, 10% for training, validation, and testing and randomly divide up the data.
div.spiral.pts<-spiral.pts %>%
mutate(
# generate a random number uniformally between 0 and 1
rand_value = runif(nrow(spiral.pts)),
# divide the data based on how high this number is into different groups
data_div = ifelse(rand_value<0.6,"Training",
ifelse(rand_value<0.9,"Validation",
"Testing")
)
) %>% select(-rand_value) # we don't need this anymore
div.spiral.pts %>% sample_n(4) %>% kable(digits=2)
x1 | y1 | class | data_div | |
---|---|---|---|---|
71 | 30.00 | 0.00 | Sad | Training |
35 | 15.91 | 1.67 | Happy | Training |
93 | -5.35 | -5.95 | Sad | Testing |
34 | 16.63 | 3.53 | Happy | Validation |
Making a decision tree can be done by providing the output (class
) as a function of the input, in this case just a combination of x1 and y1 (~x1+y1
). From this a
library(rpart)
library(rpart.plot)
training.data <- div.spiral.pts %>% subset(data_div == "Training")
dec.tree<-rpart(class~x1+y1,data=training.data)
A tree can be visualized graphically as a trunk (the top most node) dividing progressively into smaller subnodes
or as a list of rules to apply
## n= 125
##
## node), split, n, loss, yval, (yprob)
## * denotes terminal node
##
## 1) root 125 57 Happy (0.54400000 0.45600000)
## 2) y1< 33.01294 116 48 Happy (0.58620690 0.41379310)
## 4) y1>=18.4582 14 0 Happy (1.00000000 0.00000000) *
## 5) y1< 18.4582 102 48 Happy (0.52941176 0.47058824)
## 10) y1< 5.691917 82 31 Happy (0.62195122 0.37804878)
## 20) y1>=-31.22271 75 25 Happy (0.66666667 0.33333333)
## 40) y1>=3.447282 10 0 Happy (1.00000000 0.00000000) *
## 41) y1< 3.447282 65 25 Happy (0.61538462 0.38461538)
## 82) y1< -18.4582 13 1 Happy (0.92307692 0.07692308) *
## 83) y1>=-18.4582 52 24 Happy (0.53846154 0.46153846)
## 166) y1>=-10.0245 34 11 Happy (0.67647059 0.32352941) *
## 167) y1< -10.0245 18 5 Sad (0.27777778 0.72222222) *
## 21) y1< -31.22271 7 1 Sad (0.14285714 0.85714286) *
## 11) y1>=5.691917 20 3 Sad (0.15000000 0.85000000) *
## 3) y1>=33.01294 9 0 Sad (0.00000000 1.00000000) *
Overlaying with the prediction data looks good
It struggles more with the validation data since it has never seen it before and it’s not quite the same as the training
The test data (normally we would not look at it at all right now and wait until the very end) looks even worse and an even smaller fraction is correctly matched.
We can choose to make more complicated trees by changing the function to something more detailed like
\[ class = x1+y1+x1^2+y1^2+\sin(x1/5)+\sin(y1/5) \]
dec.tree<-rpart(class~x1+y1+x1^2+y1^2+sin(x1/5)+sin(y1/5),data=training.data)
prp(dec.tree)
How does a standard image processing chain look?
Given the complexity of the tree, we need to do some pruning
With a quantitative approach, we can calculate the specific shape or distribution metrics on the sample with each parameter and establish the relationship between parameter and metric.
The way we do this is usually a parameter sweep which means taking one (or more) parameters and varying them between the reasonable bounds (judged qualitatively).
Sensitivity is defined in control system theory as the change in the value of an output against the change in the input. \[ S = \frac{|\Delta \textrm{Metric}|}{|\Delta \textrm{Parameter}|} \]
Such a strict definition is not particularly useful for image processing since a threshold has a unit of intensity and a metric might be volume which has \(m^3\) so the sensitivity becomes volume per intensity.
A more common approach is to estimate the variation in this parameter between images or within a single image (automatic threshold methods can be useful for this) and define the sensitivity based on this variation. It is also common to normalize it with the mean value so the result is a percentage.
\[ S = \frac{max(\textrm{Metric})-min(\textrm{Metric})}{avg(\textrm{Metric})} \]
In this graph it is magnitude of the slope. The steeper the slope the more the metric changes given a small change in the parameter
Comparing Different Variables we see that the best (lowest) value for the count sensitivity is the highest for the volume and anisotropy.
In computer programming, unit testing is a method by which individual units of source code, sets of one or more computer program modules together with associated control data, usage procedures, and operating procedures, are tested to determine if they are fit for use.
The first requirement for unit testing to work well is to have you tools divided up into small independent parts (functions) - Each part can then be tested independently (unit testing) - If the tests are well done, units can be changed and tested independently - Makes upgrading or expanding tools easy - The entire path can be tested (integration testing) - Catches mistakes in integration or glue
Ideally with realistic but simulated test data - The utility of the testing is only as good as the tests you make
function vxCnt=countVoxs(inImage)
assert countVoxs(zeros(3,3)) == 0
assert countVoxs(zeros(3,3,3)) == 0
assert countVoxs(eye(3)) == 3
function shapeTable=shapeAnalysis(inImage)
We should decompose the task into sub-componentsfunction clImage=componentLabel(inImage)
function objInfo=analyzeObject(inObject)
function vxCnt=countVoxs(inObject)
function covMat=calculateCOV(inObject)
function shapeT=calcShapeT(covMat)
function angle=calcOrientation(shapeT)
function aniso=calcAnisotropy(shapeT)
Read more and Here The Java-based unit-testing can be used (JUnit) before any of the plugins are compiled, additionally entire workflows can be made to test the objects using special testing nodes like - difference node (check the if two values are different)
KNIME Tests
Test Driven programming is a style or approach to programming where the tests are written before the functional code. Like very concrete specifications. It is easy to estimate how much time is left since you can automatically see how many of the tests have been passed. You and your collaborators are clear on the utility of the system.
One of the biggest problems with big sciences is trying to visualize a lot of heterogeneous data.
There are too many graphs which say
Linkage
Linkage 2
Too much data makes it very difficult to derive a clear message
Filter and reduce information until it is extremely simple
If we develop a consistent way of expressing graphics (sentences) in terms of elements (words) we can compose and decompose graphics easily
The most important modern work in graphical grammars is “The Grammar of Graphics” by Wilkinson, Anand, and Grossman (2005). This work built on earlier work by Bertin (1983) and proposed a grammar that can be used to describe and construct a wide range of statistical graphics.
This can be applied in R using the ggplot2 library (H. Wickham. ggplot2: elegant graphics for data analysis. Springer New York, 2009.)
Normally we think of plots in terms of some sort of data which is fed into a plot command that produces a picture - In Excel you select a range and plot-type and click “Make” - In Matlab you run plot(xdata,ydata,color/shape)
Separate the graph into its component parts
Graph Decomposed
Construct graphics by focusing on each portion independently.
They are rarely bearers of good news
Simulations (even simple ones) are very helpful (see StatisticalSignificanceHunter)
Try and understand the tests you are performing