This is an R Markdown Notebook. When you execute code within the notebook, the results appear beneath the code.
Try executing this chunk by clicking the Run button within the chunk or by placing your cursor inside it and pressing Cmd+Shift+Enter.
rr test_0_1=mnist_test[,mnist_test[nrow(mnist_test),]==0|mnist_test[nrow(mnist_test),]==1] test_3_5=mnist_test[,mnist_test[nrow(mnist_test),]==3|mnist_test[nrow(mnist_test),]==5] train_0_1=mnist_train[,mnist_train[nrow(mnist_train),]==0|mnist_train[nrow(mnist_train),]==1] train_3_5=mnist_train[,mnist_train[nrow(mnist_train),]==3|mnist_train[nrow(mnist_train),]==5]
rr true_label_test_0_1=test_0_1[nrow(test_0_1),] true_label_test_3_5=test_3_5[nrow(test_3_5),] true_label_train_0_1=train_0_1[nrow(train_0_1),] true_label_train_3_5=train_3_5[nrow(train_3_5),]
rr test_0_1=test_0_1[-nrow(test_0_1),] test_3_5=test_3_5[-nrow(test_3_5),] train_0_1=train_0_1[-nrow(train_0_1),] train_3_5=train_3_5[-nrow(train_3_5),]
rr image((1:28), (1:28), matrix(data=as.numeric(train_0_1[,true_label_train_0_1[1,]==0][,1]), nrow=sqrt(nrow(train_0_1)), ncol=sqrt(nrow(train_0_1))), col=gray.colors(256))

rr image((1:28), (1:28), matrix(data=as.numeric(train_0_1[,true_label_train_0_1[1,]==1][,1]), nrow=sqrt(nrow(train_0_1)), ncol=sqrt(nrow(train_0_1))), col=gray.colors(256))

rr image((1:28), (1:28), matrix(data=as.numeric(train_3_5[,true_label_train_3_5[1,]==3][,1]), nrow=sqrt(nrow(train_3_5)), ncol=sqrt(nrow(train_3_5))), col=gray.colors(256))

rr image((1:28), (1:28), matrix(data=as.numeric(train_3_5[,true_label_train_3_5[1,]==5][,1]), nrow=sqrt(nrow(train_3_5)), ncol=sqrt(nrow(train_3_5))), col=gray.colors(256))

- Theory
- Formula for the gradient of loss function in Logistic Regression Below is the formula for gradient descent process. \[\theta_j := \theta_j - \alpha \frac {1}{m}\sum\limits_{i = 1}^m (\frac {1}{1 + \exp(- {\theta} ^\intercal x^i)} -y^i)x_j^i\] In the formula: \(\theta_j'\)is the updated value of the parameter \(\theta_j\) for the cost function.
\(\theta_j\) is one of the parameter in the parameter vector \(\theta\)
\(\theta\) is the parameter vector to be optmized in the cost function
\(\alpha\) is the learning rate
\(m\) is the number of training data
\(x^i\) is one vector of the training data vectors
\(y^i\) is true class/label for the training data vector \(x^i\)
\(x_j^i\) is the x value for parameter \(\theta_j\)
- Pseudocode for training a Logistic Regression model
matrix_x=matrix(data=input_data, row_number=nrow(training_data), column_number=ncol(training_data)) matrix_y=matrix(data=labels, row_number=nrow(training_data), column_number=1) matrix_theta=matrix(data=initial_theta, row_number=ncol(training_data), column_number=1)
gradient_function=function(matrix_x, matrix_y,matrix_theta){ theta=matrix_theta-alpha(1/nrow(training_data))transpose(matrix_x)%%(1/(1+exp(-matrix_x%*%matrix_theta))-matrix_y) return(theta) }
sigmoid_function=function(z){ g=1/(1+exp(-z)) return(g) }
cost_function=function(matrix_theta){ g=sigmoid_function(matrix_x%%matrix_theta) cost_J=((transpose(-matrix_y)%%log(g))-(transpose(1-matrix_y)%*%log(1-g))) return(cost_J) }
while(J>threshold){ matrix_theta=gradient_function(matrix_x, matrix_y, matrix_theta) J=cost_function(matrix_theta) }
- Calculate the number of operations per gradient descent iteration
- Implementation
sigmoid=function(z){
g=1/(1+exp(-z))
return(g)
}
logit_model=function(input_data, class_data, theta_initial, alpha,epsilon ){
if(length(unique(unlist(class_data)))==2){
init=0
for(i in unique(unlist(class_data)) ){
if(!(i%in%c(0,1))) {
class_data[class_data==i]=init
init=init+1
}}
}
x=t(as.matrix(input_data))
y=t(as.matrix(class_data))
theta=matrix(data=rep(theta_initial, ncol(x)), nrow=ncol(x), ncol=1)
J=epsilon+1
while(J>epsilon)
{
theta=theta-alpha*(t(x)%*%(1/(1+exp(-x%*%theta))-y)/(nrow(x)))
J=(t(-y)%*%log(sigmoid(x%*%theta))-t(1-y)%*%log(1-sigmoid(x%*%theta)))
print(J)
}
return(theta)
}
model_test=function(theta, training_input_data, training_class_data, test_input_data, test_class_data){
if(length(unique(unlist(test_class_data)))==2){
init=0
for(i in unique(unlist(test_class_data)) ){
if(!(i%in%c(0,1))) {
test_class_data[test_class_data==i]=init
init=init+1
}}
}
if(length(unique(unlist(training_class_data)))==2){
init=0
for(i in unique(unlist(training_class_data)) ){
if(!(i%in%c(0,1))) {
training_class_data[training_class_data==i]=init
init=init+1
}}
}
prediction_train=round(sigmoid(t(as.matrix(training_input_data))%*%theta))
error_train=sum(abs(prediction_train-t(as.matrix(training_class_data))))/nrow(t(as.matrix(training_class_data)))
prediction_test=round(sigmoid(t(as.matrix(test_input_data))%*%theta))
error_test=sum(abs(prediction_test-t(as.matrix(test_class_data))))/nrow(t(as.matrix(test_class_data)))
print(cat("Accuracy of Training Set: ", 1-error_train))
print(cat("Accuracy of Test Set: ", 1-error_test))
return(c(1-error_train, 1-error_test))
}
theta_0_1=logit_model(train_0_1,true_label_train_0_1, 0.1,2, 50)
rr model_test(theta, train_0_1, true_label_train_0_1, test_0_1, true_label_test_0_1)
Accuracy of Training Set: 0.9987367NULL
Accuracy of Test Set: 0.9995272NULL
theta_3_5=logit_model(train_3_5,true_label_train_3_5, 0.1 ,2, 70)
model_test(theta_3_5, train_3_5, true_label_train_3_5, test_3_5, new_label_test_3_5)
sample_and_shuffle=function(data, sample_perc){
new_data=sample(data, sample_perc*(ncol(data)), replace=FALSE)
return(new_data)
}
repeat_modeling=function(training_data, test_data, class,repeat_time, size,size_step, initial_theta, alpha, cost){
result=c()
for(i in c(1:repeat_time)){
spsf_train=sample_and_shuffle(training_data,size)
if(identical(unlist(class),unlist(c(3,5)))){
spsf_train=spsf_train[,spsf_train[nrow(spsf_train),]==3|spsf_train[nrow(spsf_train),]==5]
true_label_spsf_train=spsf_train[nrow(spsf_train),]
spsf_train=spsf_train[-nrow(spsf_train),]
spsf_test=test_data
spsf_test=spsf_test[,spsf_test[nrow(spsf_test),]==3|spsf_test[nrow(spsf_test),]==5]
true_label_spsf_test=spsf_test[nrow(spsf_test),]
spsf_test=spsf_test[-nrow(spsf_test),]
}
else if(identical(unlist(class),unlist(c(0,1)))){
spsf_train=spsf_train[,spsf_train[nrow(spsf_train),]==0|spsf_train[nrow(spsf_train),]==1]
true_label_spsf_train=spsf_train[nrow(spsf_train),]
spsf_train=spsf_train[-nrow(spsf_train),]
spsf_test=test_data
spsf_test=spsf_test[,spsf_test[nrow(spsf_test),]==0|spsf_test[nrow(spsf_test),]==1]
true_label_spsf_test=spsf_test[nrow(spsf_test),]
spsf_test=spsf_test[-nrow(spsf_test),]
} else{ print("Wrong Class")}
print(nrow(t(spsf_train)))
print(nrow(t(true_label_spsf_train)))
print(nrow(t(spsf_test)))
print(nrow(t(true_label_spsf_test)))
spsf_theta=logit_model(spsf_train,true_label_spsf_train, initial_theta,alpha, cost)
accuracy=model_test(spsf_theta, spsf_train,true_label_spsf_train, spsf_test, true_label_spsf_test)
result=rbind(result,c(i, size, initial_theta, alpha, cost, accuracy[1],accuracy[2]))
size=size-size_step
}
return(result)
}
- Training
Training 2 models: train_0_1 train_3_5
Repeat for 10 times
ten_times_0_1=repeat_modeling(mnist_train, mnist_test, c(0,1),10, 1,0.1, 0.1, 0.8, 100)
ten_times_3_5=repeat_modeling(mnist_train, mnist_test, c(3,5),10, 1,0.1, 0.1, 1.4, 1000)
Explain the difference
Explain what to do with multiple classes
- Evaluation
Experiment with different initializations
Experiment with different convergence criteria for gradient descent
- Learning Curves
Add a new chunk by clicking the Insert Chunk button on the toolbar or by pressing Cmd+Option+I.
When you save the notebook, an HTML file containing the code and output will be saved alongside it (click the Preview button or press Cmd+Shift+K to preview the HTML file).
LS0tCnRpdGxlOiAiSG9tZXdvcmsgMyIKb3V0cHV0OiBodG1sX25vdGVib29rCi0tLQoKVGhpcyBpcyBhbiBbUiBNYXJrZG93bl0oaHR0cDovL3JtYXJrZG93bi5yc3R1ZGlvLmNvbSkgTm90ZWJvb2suIFdoZW4geW91IGV4ZWN1dGUgY29kZSB3aXRoaW4gdGhlIG5vdGVib29rLCB0aGUgcmVzdWx0cyBhcHBlYXIgYmVuZWF0aCB0aGUgY29kZS4gCgpUcnkgZXhlY3V0aW5nIHRoaXMgY2h1bmsgYnkgY2xpY2tpbmcgdGhlICpSdW4qIGJ1dHRvbiB3aXRoaW4gdGhlIGNodW5rIG9yIGJ5IHBsYWNpbmcgeW91ciBjdXJzb3IgaW5zaWRlIGl0IGFuZCBwcmVzc2luZyAqQ21kK1NoaWZ0K0VudGVyKi4gCgpgYGB7cn0KbW5pc3RfdHJhaW49cmVhZC5jc3YoIm1uaXN0X3RyYWluLmNzdiIsIGhlYWRlcj1GQUxTRSkKbW5pc3RfdGVzdD1yZWFkLmNzdigibW5pc3RfdGVzdC5jc3YiLCBoZWFkZXI9RkFMU0UpCmBgYAoKYGBge3J9CnRlc3RfMF8xPW1uaXN0X3Rlc3RbLG1uaXN0X3Rlc3RbbnJvdyhtbmlzdF90ZXN0KSxdPT0wfG1uaXN0X3Rlc3RbbnJvdyhtbmlzdF90ZXN0KSxdPT0xXQp0ZXN0XzNfNT1tbmlzdF90ZXN0WyxtbmlzdF90ZXN0W25yb3cobW5pc3RfdGVzdCksXT09M3xtbmlzdF90ZXN0W25yb3cobW5pc3RfdGVzdCksXT09NV0KdHJhaW5fMF8xPW1uaXN0X3RyYWluWyxtbmlzdF90cmFpbltucm93KG1uaXN0X3RyYWluKSxdPT0wfG1uaXN0X3RyYWluW25yb3cobW5pc3RfdHJhaW4pLF09PTFdCnRyYWluXzNfNT1tbmlzdF90cmFpblssbW5pc3RfdHJhaW5bbnJvdyhtbmlzdF90cmFpbiksXT09M3xtbmlzdF90cmFpbltucm93KG1uaXN0X3RyYWluKSxdPT01XQpgYGAKCmBgYHtyfQp0cnVlX2xhYmVsX3Rlc3RfMF8xPXRlc3RfMF8xW25yb3codGVzdF8wXzEpLF0KdHJ1ZV9sYWJlbF90ZXN0XzNfNT10ZXN0XzNfNVtucm93KHRlc3RfM181KSxdCnRydWVfbGFiZWxfdHJhaW5fMF8xPXRyYWluXzBfMVtucm93KHRyYWluXzBfMSksXQp0cnVlX2xhYmVsX3RyYWluXzNfNT10cmFpbl8zXzVbbnJvdyh0cmFpbl8zXzUpLF0KYGBgCgpgYGB7cn0KdGVzdF8wXzE9dGVzdF8wXzFbLW5yb3codGVzdF8wXzEpLF0KdGVzdF8zXzU9dGVzdF8zXzVbLW5yb3codGVzdF8zXzUpLF0KdHJhaW5fMF8xPXRyYWluXzBfMVstbnJvdyh0cmFpbl8wXzEpLF0KdHJhaW5fM181PXRyYWluXzNfNVstbnJvdyh0cmFpbl8zXzUpLF0KYGBgCgpgYGB7cn0KaW1hZ2UoKDE6MjgpLCAoMToyOCksIG1hdHJpeChkYXRhPWFzLm51bWVyaWModHJhaW5fMF8xWyx0cnVlX2xhYmVsX3RyYWluXzBfMVsxLF09PTBdWywxXSksIG5yb3c9c3FydChucm93KHRyYWluXzBfMSkpLCBuY29sPXNxcnQobnJvdyh0cmFpbl8wXzEpKSksIGNvbD1ncmF5LmNvbG9ycygyNTYpKQpgYGAKCmBgYHtyfQppbWFnZSgoMToyOCksICgxOjI4KSwgbWF0cml4KGRhdGE9YXMubnVtZXJpYyh0cmFpbl8wXzFbLHRydWVfbGFiZWxfdHJhaW5fMF8xWzEsXT09MV1bLDFdKSwgbnJvdz1zcXJ0KG5yb3codHJhaW5fMF8xKSksIG5jb2w9c3FydChucm93KHRyYWluXzBfMSkpKSwgY29sPWdyYXkuY29sb3JzKDI1NikpCmBgYAoKYGBge3J9CmltYWdlKCgxOjI4KSwgKDE6MjgpLCBtYXRyaXgoZGF0YT1hcy5udW1lcmljKHRyYWluXzNfNVssdHJ1ZV9sYWJlbF90cmFpbl8zXzVbMSxdPT0zXVssMV0pLCBucm93PXNxcnQobnJvdyh0cmFpbl8zXzUpKSwgbmNvbD1zcXJ0KG5yb3codHJhaW5fM181KSkpLCBjb2w9Z3JheS5jb2xvcnMoMjU2KSkKYGBgCgpgYGB7cn0KaW1hZ2UoKDE6MjgpLCAoMToyOCksIG1hdHJpeChkYXRhPWFzLm51bWVyaWModHJhaW5fM181Wyx0cnVlX2xhYmVsX3RyYWluXzNfNVsxLF09PTVdWywxXSksIG5yb3c9c3FydChucm93KHRyYWluXzNfNSkpLCBuY29sPXNxcnQobnJvdyh0cmFpbl8zXzUpKSksIGNvbD1ncmF5LmNvbG9ycygyNTYpKQpgYGAKCjEuIFRoZW9yeQphLiBGb3JtdWxhIGZvciB0aGUgZ3JhZGllbnQgb2YgbG9zcyBmdW5jdGlvbiBpbiBMb2dpc3RpYyBSZWdyZXNzaW9uCkJlbG93IGlzIHRoZSBmb3JtdWxhIGZvciBncmFkaWVudCBkZXNjZW50IHByb2Nlc3MuCiQkXHRoZXRhX2ogOj0gXHRoZXRhX2ogLSBcYWxwaGEgXGZyYWMgezF9e219XHN1bVxsaW1pdHNfe2kgPSAxfV5tIChcZnJhYyB7MX17MSDvvIsgXGV4cCjvvI0ge1x0aGV0YX0gXlxpbnRlcmNhbCB4XmkpfSAteV5pKXhfal5pJCQKSW4gdGhlIGZvcm11bGE6CiAgICRcdGhldGFfaickaXMgdGhlIHVwZGF0ZWQgdmFsdWUgb2YgdGhlIHBhcmFtZXRlciAkXHRoZXRhX2okIGZvciB0aGUgY29zdCBmdW5jdGlvbi4KCiAgICRcdGhldGFfaiQgaXMgb25lIG9mIHRoZSBwYXJhbWV0ZXIgaW4gdGhlIHBhcmFtZXRlciB2ZWN0b3IgJFx0aGV0YSQKCiAgICRcdGhldGEkIGlzIHRoZSBwYXJhbWV0ZXIgdmVjdG9yIHRvIGJlIG9wdG1pemVkIGluIHRoZSBjb3N0IGZ1bmN0aW9uCgogICAkXGFscGhhJCBpcyB0aGUgbGVhcm5pbmcgcmF0ZQoKICAgJG0kIGlzIHRoZSBudW1iZXIgb2YgdHJhaW5pbmcgZGF0YQoKICAgJHheaSQgaXMgb25lIHZlY3RvciBvZiB0aGUgdHJhaW5pbmcgZGF0YSB2ZWN0b3JzCgogICAkeV5pJCBpcyB0cnVlIGNsYXNzL2xhYmVsIGZvciB0aGUgdHJhaW5pbmcgZGF0YSB2ZWN0b3IgJHheaSQKCiAgICR4X2peaSQgaXMgdGhlIHggdmFsdWUgZm9yIHBhcmFtZXRlciAgJFx0aGV0YV9qJAoKCgoKCmIuIFBzZXVkb2NvZGUgZm9yIHRyYWluaW5nIGEgTG9naXN0aWMgUmVncmVzc2lvbiBtb2RlbAoKICAgbWF0cml4X3g9bWF0cml4KGRhdGE9aW5wdXRfZGF0YSwgcm93X251bWJlcj1ucm93KHRyYWluaW5nX2RhdGEpLCBjb2x1bW5fbnVtYmVyPW5jb2wodHJhaW5pbmdfZGF0YSkpCiAgIG1hdHJpeF95PW1hdHJpeChkYXRhPWxhYmVscywgcm93X251bWJlcj1ucm93KHRyYWluaW5nX2RhdGEpLCBjb2x1bW5fbnVtYmVyPTEpCiAgIG1hdHJpeF90aGV0YT1tYXRyaXgoZGF0YT1pbml0aWFsX3RoZXRhLCByb3dfbnVtYmVyPW5jb2wodHJhaW5pbmdfZGF0YSksIGNvbHVtbl9udW1iZXI9MSkKICAgCiAgIGdyYWRpZW50X2Z1bmN0aW9uPWZ1bmN0aW9uKG1hdHJpeF94LCBtYXRyaXhfeSxtYXRyaXhfdGhldGEpewogICB0aGV0YT1tYXRyaXhfdGhldGEtYWxwaGEqKDEvbnJvdyh0cmFpbmluZ19kYXRhKSl0cmFuc3Bvc2UobWF0cml4X3gpJSolKDEvKDErZXhwKC1tYXRyaXhfeCUqJW1hdHJpeF90aGV0YSkpLW1hdHJpeF95KQogICByZXR1cm4odGhldGEpCiAgIH0KICAgCiAgIHNpZ21vaWRfZnVuY3Rpb249ZnVuY3Rpb24oeil7CiAgIGc9MS8oMStleHAoLXopKQogICByZXR1cm4oZykKICAgfQogICAKICAgY29zdF9mdW5jdGlvbj1mdW5jdGlvbihtYXRyaXhfdGhldGEpewogICBnPXNpZ21vaWRfZnVuY3Rpb24obWF0cml4X3glKiVtYXRyaXhfdGhldGEpCiAgIGNvc3RfSj0oKHRyYW5zcG9zZSgtbWF0cml4X3kpJSolbG9nKGcpKS0odHJhbnNwb3NlKDEtbWF0cml4X3kpJSolbG9nKDEtZykpKQogICByZXR1cm4oY29zdF9KKQogICB9CgogICB3aGlsZShKPnRocmVzaG9sZCl7CiAgIG1hdHJpeF90aGV0YT1ncmFkaWVudF9mdW5jdGlvbihtYXRyaXhfeCwgbWF0cml4X3ksIG1hdHJpeF90aGV0YSkKICAgSj1jb3N0X2Z1bmN0aW9uKG1hdHJpeF90aGV0YSkKICAgfQoKCmMuIENhbGN1bGF0ZSB0aGUgbnVtYmVyIG9mIG9wZXJhdGlvbnMgcGVyIGdyYWRpZW50IGRlc2NlbnQgaXRlcmF0aW9uCgoyLiBJbXBsZW1lbnRhdGlvbgoKYGBge3J9CnNpZ21vaWQ9ZnVuY3Rpb24oeil7CiAgZz0xLygxK2V4cCgteikpCiAgcmV0dXJuKGcpCn0KCgpsb2dpdF9tb2RlbD1mdW5jdGlvbihpbnB1dF9kYXRhLCBjbGFzc19kYXRhLCB0aGV0YV9pbml0aWFsLCBhbHBoYSxlcHNpbG9uICl7CiAgCiAgaWYobGVuZ3RoKHVuaXF1ZSh1bmxpc3QoY2xhc3NfZGF0YSkpKT09Mil7CiAgICAKICAgIGluaXQ9MAogICAgZm9yKGkgaW4gdW5pcXVlKHVubGlzdChjbGFzc19kYXRhKSkgKXsKICAgICAgaWYoIShpJWluJWMoMCwxKSkpIHsgCiAgICAgICAgY2xhc3NfZGF0YVtjbGFzc19kYXRhPT1pXT1pbml0CiAgICAgICAgaW5pdD1pbml0KzEKICAgICAgICAKICAgICAgfX0gIAogIH0KICAKICAKICB4PXQoYXMubWF0cml4KGlucHV0X2RhdGEpKQogIHk9dChhcy5tYXRyaXgoY2xhc3NfZGF0YSkpCiAgdGhldGE9bWF0cml4KGRhdGE9cmVwKHRoZXRhX2luaXRpYWwsIG5jb2woeCkpLCBucm93PW5jb2woeCksIG5jb2w9MSkKICBKPWVwc2lsb24rMQogIHdoaWxlKEo+ZXBzaWxvbikKICB7CiAgICAKICAgIHRoZXRhPXRoZXRhLWFscGhhKih0KHgpJSolKDEvKDErZXhwKC14JSoldGhldGEpKS15KS8obnJvdyh4KSkpCiAgICBKPSh0KC15KSUqJWxvZyhzaWdtb2lkKHglKiV0aGV0YSkpLXQoMS15KSUqJWxvZygxLXNpZ21vaWQoeCUqJXRoZXRhKSkpCiAgICBwcmludChKKQogIH0KICByZXR1cm4odGhldGEpCn0KYGBgCgpgYGB7cn0KbW9kZWxfdGVzdD1mdW5jdGlvbih0aGV0YSwgdHJhaW5pbmdfaW5wdXRfZGF0YSwgdHJhaW5pbmdfY2xhc3NfZGF0YSwgdGVzdF9pbnB1dF9kYXRhLCB0ZXN0X2NsYXNzX2RhdGEpewogIAogIGlmKGxlbmd0aCh1bmlxdWUodW5saXN0KHRlc3RfY2xhc3NfZGF0YSkpKT09Mil7CiAgICAKICAgIGluaXQ9MAogICAgZm9yKGkgaW4gdW5pcXVlKHVubGlzdCh0ZXN0X2NsYXNzX2RhdGEpKSApewogICAgICBpZighKGklaW4lYygwLDEpKSkgeyAKICAgICAgICB0ZXN0X2NsYXNzX2RhdGFbdGVzdF9jbGFzc19kYXRhPT1pXT1pbml0CiAgICAgICAgaW5pdD1pbml0KzEKICAgICAgICAKICAgICAgfX0gIAogIH0KICBpZihsZW5ndGgodW5pcXVlKHVubGlzdCh0cmFpbmluZ19jbGFzc19kYXRhKSkpPT0yKXsKICAgIAogICAgaW5pdD0wCiAgICBmb3IoaSBpbiB1bmlxdWUodW5saXN0KHRyYWluaW5nX2NsYXNzX2RhdGEpKSApewogICAgICBpZighKGklaW4lYygwLDEpKSkgeyAKICAgICAgICB0cmFpbmluZ19jbGFzc19kYXRhW3RyYWluaW5nX2NsYXNzX2RhdGE9PWldPWluaXQKICAgICAgICBpbml0PWluaXQrMQogICAgICAgIAogICAgICB9fSAgCiAgfQogIAogIHByZWRpY3Rpb25fdHJhaW49cm91bmQoc2lnbW9pZCh0KGFzLm1hdHJpeCh0cmFpbmluZ19pbnB1dF9kYXRhKSklKiV0aGV0YSkpCiAgZXJyb3JfdHJhaW49c3VtKGFicyhwcmVkaWN0aW9uX3RyYWluLXQoYXMubWF0cml4KHRyYWluaW5nX2NsYXNzX2RhdGEpKSkpL25yb3codChhcy5tYXRyaXgodHJhaW5pbmdfY2xhc3NfZGF0YSkpKQogIHByZWRpY3Rpb25fdGVzdD1yb3VuZChzaWdtb2lkKHQoYXMubWF0cml4KHRlc3RfaW5wdXRfZGF0YSkpJSoldGhldGEpKQogIGVycm9yX3Rlc3Q9c3VtKGFicyhwcmVkaWN0aW9uX3Rlc3QtdChhcy5tYXRyaXgodGVzdF9jbGFzc19kYXRhKSkpKS9ucm93KHQoYXMubWF0cml4KHRlc3RfY2xhc3NfZGF0YSkpKQogIAogIHByaW50KGNhdCgiQWNjdXJhY3kgb2YgVHJhaW5pbmcgU2V0OiAiLCAxLWVycm9yX3RyYWluKSkKICBwcmludChjYXQoIkFjY3VyYWN5IG9mIFRlc3QgU2V0OiAiLCAxLWVycm9yX3Rlc3QpKQogIHJldHVybihjKDEtZXJyb3JfdHJhaW4sIDEtZXJyb3JfdGVzdCkpCn0KCmBgYAoKYGBge3J9CnRoZXRhXzBfMT1sb2dpdF9tb2RlbCh0cmFpbl8wXzEsdHJ1ZV9sYWJlbF90cmFpbl8wXzEsIDAuMSwyLCA1MCkKYGBgCgpgYGB7cn0KCmBgYAoKYGBge3J9Cm1vZGVsX3Rlc3QodGhldGFfMF8xLCB0cmFpbl8wXzEsIHRydWVfbGFiZWxfdHJhaW5fMF8xLCB0ZXN0XzBfMSwgdHJ1ZV9sYWJlbF90ZXN0XzBfMSkKYGBgCgoKCmBgYHtyfQoKCnRoZXRhXzNfNT1sb2dpdF9tb2RlbCh0cmFpbl8zXzUsdHJ1ZV9sYWJlbF90cmFpbl8zXzUsIDAuMSAsMiwgNzApCmBgYAoKCmBgYHtyfQptb2RlbF90ZXN0KHRoZXRhXzNfNSwgdHJhaW5fM181LCB0cnVlX2xhYmVsX3RyYWluXzNfNSwgdGVzdF8zXzUsIG5ld19sYWJlbF90ZXN0XzNfNSkKYGBgCgpgYGB7cn0KCnNhbXBsZV9hbmRfc2h1ZmZsZT1mdW5jdGlvbihkYXRhLCBzYW1wbGVfcGVyYyl7CiAgbmV3X2RhdGE9c2FtcGxlKGRhdGEsIHNhbXBsZV9wZXJjKihuY29sKGRhdGEpKSwgcmVwbGFjZT1GQUxTRSkKICByZXR1cm4obmV3X2RhdGEpCn0KCmBgYAoKCgoKCgpgYGB7cn0KcmVwZWF0X21vZGVsaW5nPWZ1bmN0aW9uKHRyYWluaW5nX2RhdGEsIHRlc3RfZGF0YSwgY2xhc3MscmVwZWF0X3RpbWUsIHNpemUsc2l6ZV9zdGVwLCBpbml0aWFsX3RoZXRhLCBhbHBoYSwgY29zdCl7CgpyZXN1bHQ9YygpICAKZm9yKGkgaW4gYygxOnJlcGVhdF90aW1lKSl7CnNwc2ZfdHJhaW49c2FtcGxlX2FuZF9zaHVmZmxlKHRyYWluaW5nX2RhdGEsc2l6ZSkKaWYoaWRlbnRpY2FsKHVubGlzdChjbGFzcyksdW5saXN0KGMoMyw1KSkpKXsKc3BzZl90cmFpbj1zcHNmX3RyYWluWyxzcHNmX3RyYWluW25yb3coc3BzZl90cmFpbiksXT09M3xzcHNmX3RyYWluW25yb3coc3BzZl90cmFpbiksXT09NV0KdHJ1ZV9sYWJlbF9zcHNmX3RyYWluPXNwc2ZfdHJhaW5bbnJvdyhzcHNmX3RyYWluKSxdCnNwc2ZfdHJhaW49c3BzZl90cmFpblstbnJvdyhzcHNmX3RyYWluKSxdCgpzcHNmX3Rlc3Q9dGVzdF9kYXRhCnNwc2ZfdGVzdD1zcHNmX3Rlc3RbLHNwc2ZfdGVzdFtucm93KHNwc2ZfdGVzdCksXT09M3xzcHNmX3Rlc3RbbnJvdyhzcHNmX3Rlc3QpLF09PTVdCnRydWVfbGFiZWxfc3BzZl90ZXN0PXNwc2ZfdGVzdFtucm93KHNwc2ZfdGVzdCksXQpzcHNmX3Rlc3Q9c3BzZl90ZXN0Wy1ucm93KHNwc2ZfdGVzdCksXQp9CmVsc2UgaWYoaWRlbnRpY2FsKHVubGlzdChjbGFzcyksdW5saXN0KGMoMCwxKSkpKXsKc3BzZl90cmFpbj1zcHNmX3RyYWluWyxzcHNmX3RyYWluW25yb3coc3BzZl90cmFpbiksXT09MHxzcHNmX3RyYWluW25yb3coc3BzZl90cmFpbiksXT09MV0KdHJ1ZV9sYWJlbF9zcHNmX3RyYWluPXNwc2ZfdHJhaW5bbnJvdyhzcHNmX3RyYWluKSxdCnNwc2ZfdHJhaW49c3BzZl90cmFpblstbnJvdyhzcHNmX3RyYWluKSxdCgpzcHNmX3Rlc3Q9dGVzdF9kYXRhCnNwc2ZfdGVzdD1zcHNmX3Rlc3RbLHNwc2ZfdGVzdFtucm93KHNwc2ZfdGVzdCksXT09MHxzcHNmX3Rlc3RbbnJvdyhzcHNmX3Rlc3QpLF09PTFdCnRydWVfbGFiZWxfc3BzZl90ZXN0PXNwc2ZfdGVzdFtucm93KHNwc2ZfdGVzdCksXQpzcHNmX3Rlc3Q9c3BzZl90ZXN0Wy1ucm93KHNwc2ZfdGVzdCksXQp9IGVsc2V7IHByaW50KCJXcm9uZyBDbGFzcyIpfQoKcHJpbnQobnJvdyh0KHNwc2ZfdHJhaW4pKSkKcHJpbnQobnJvdyh0KHRydWVfbGFiZWxfc3BzZl90cmFpbikpKQpwcmludChucm93KHQoc3BzZl90ZXN0KSkpCnByaW50KG5yb3codCh0cnVlX2xhYmVsX3Nwc2ZfdGVzdCkpKQpzcHNmX3RoZXRhPWxvZ2l0X21vZGVsKHNwc2ZfdHJhaW4sdHJ1ZV9sYWJlbF9zcHNmX3RyYWluLCBpbml0aWFsX3RoZXRhLGFscGhhLCBjb3N0KQoKCgphY2N1cmFjeT1tb2RlbF90ZXN0KHNwc2ZfdGhldGEsIHNwc2ZfdHJhaW4sdHJ1ZV9sYWJlbF9zcHNmX3RyYWluLCBzcHNmX3Rlc3QsIHRydWVfbGFiZWxfc3BzZl90ZXN0KQoKcmVzdWx0PXJiaW5kKHJlc3VsdCxjKGksIHNpemUsIGluaXRpYWxfdGhldGEsIGFscGhhLCBjb3N0LCBhY2N1cmFjeVsxXSxhY2N1cmFjeVsyXSkpCgpzaXplPXNpemUtc2l6ZV9zdGVwCn0KCnJldHVybihyZXN1bHQpICAKfQoKYGBgCgoKMy4gVHJhaW5pbmcKCmEuIFRyYWluaW5nIDIgbW9kZWxzOgp0cmFpbl8wXzEKdHJhaW5fM181CgpiLiBSZXBlYXQgZm9yIDEwIHRpbWVzCmBgYHtyfQoKdGVuX3RpbWVzXzBfMT1yZXBlYXRfbW9kZWxpbmcobW5pc3RfdHJhaW4sIG1uaXN0X3Rlc3QsIGMoMCwxKSwxMCwgMSwwLjEsIDAuMSwgMC44LCAxMDApCnRlbl90aW1lc18zXzU9cmVwZWF0X21vZGVsaW5nKG1uaXN0X3RyYWluLCBtbmlzdF90ZXN0LCBjKDMsNSksMTAsIDEsMC4xLCAwLjEsIDEuNCwgMTAwMCkKYGBgCgpjLiBFeHBsYWluIHRoZSBkaWZmZXJlbmNlCgpkLiBFeHBsYWluIHdoYXQgdG8gZG8gd2l0aCBtdWx0aXBsZSBjbGFzc2VzCgo0LiBFdmFsdWF0aW9uCgphLiBFeHBlcmltZW50IHdpdGggZGlmZmVyZW50IGluaXRpYWxpemF0aW9ucyAKCmIuIEV4cGVyaW1lbnQgd2l0aCBkaWZmZXJlbnQgY29udmVyZ2VuY2UgY3JpdGVyaWEgZm9yIGdyYWRpZW50IGRlc2NlbnQKCjUuIExlYXJuaW5nIEN1cnZlcwoKYS4gCgoKCkFkZCBhIG5ldyBjaHVuayBieSBjbGlja2luZyB0aGUgKkluc2VydCBDaHVuayogYnV0dG9uIG9uIHRoZSB0b29sYmFyIG9yIGJ5IHByZXNzaW5nICpDbWQrT3B0aW9uK0kqLgoKV2hlbiB5b3Ugc2F2ZSB0aGUgbm90ZWJvb2ssIGFuIEhUTUwgZmlsZSBjb250YWluaW5nIHRoZSBjb2RlIGFuZCBvdXRwdXQgd2lsbCBiZSBzYXZlZCBhbG9uZ3NpZGUgaXQgKGNsaWNrIHRoZSAqUHJldmlldyogYnV0dG9uIG9yIHByZXNzICpDbWQrU2hpZnQrSyogdG8gcHJldmlldyB0aGUgSFRNTCBmaWxlKS4K