├── fig.jpg ├── chainer ├── mean-std-20170714.csv ├── funcReadLocations.py ├── test.sh ├── train.sh ├── forwardFunc_IshiiModel.py ├── funcLoadBatcFiles.py ├── megasolar_model_ishii.py ├── funcMakeNormFiles.py ├── trainMegaSolarData_IshiiModel_LoadAll.py └── testMegaSolarData_IshiiModel_LoadAll_outIoU.py ├── torch ├── train_size16_meanstd.csv ├── train-cnn.sh ├── convertGPUToCPU.lua ├── test-cnn.sh ├── test_hdf5_list.txt ├── ComputeCM.py ├── test-cnn-cpu.lua └── train-cnn.lua ├── LICENSE.md └── README.md /fig.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gistairc/MUSIC4P3/HEAD/fig.jpg -------------------------------------------------------------------------------- /chainer/mean-std-20170714.csv: -------------------------------------------------------------------------------- 1 | 10523.2,2727.81 2 | 9618.03,2866.96 3 | 8597.75,2906.9 4 | 7786.07,3190.72 5 | 12724.9,6492.82 6 | 9153.44,3817.24 7 | 7339.11,2623.78 8 | -------------------------------------------------------------------------------- /chainer/funcReadLocations.py: -------------------------------------------------------------------------------- 1 | 2 | 3 | def ReadLocations(filename): 4 | with open(filename, "r") as ins: 5 | array = [] 6 | for line in ins: 7 | array.append(line.rstrip('\n')) 8 | return array 9 | -------------------------------------------------------------------------------- /torch/train_size16_meanstd.csv: -------------------------------------------------------------------------------- 1 | 10523.166260972,2727.812099227 2 | 9618.029140872,2866.9551326075 3 | 8597.7603006359,2906.9052381504 4 | 7786.0726436288,3190.7179589405 5 | 12724.932743973,6492.823863544 6 | 9153.4478082235,3817.2383888866 7 | 7339.119508997,2623.7799687889 8 | -------------------------------------------------------------------------------- /chainer/test.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | NORM="mean-std" 4 | 5 | python testMegaSolarData_IshiiModel_LoadAll_outIoU.py \ 6 | --filelocationspath "./resource" \ 7 | --positivefilename "locationPositiveTestData.txt" \ 8 | --negativefilename "locationNegativeTestData.txt" \ 9 | --meanstdfilepath ${NORM}".csv" \ 10 | --modelFileNamePath "./result/newmegasolarCNN_NS_72720_" 11 | -------------------------------------------------------------------------------- /chainer/train.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | NORM="mean-std" 4 | 5 | mkdir result 6 | 7 | python funcMakeNormFiles.py \ 8 | --filelocationspath "./resource" \ 9 | --positivefilename "locationPositiveTrainData.txt" \ 10 | --negativefilename "locationNegativeTrainData.txt" \ 11 | --meanstdfilepath ${NORM} 12 | 13 | python trainMegaSolarData_IshiiModel_LoadAll.py \ 14 | --filelocationspath "./resource" \ 15 | --positivefilename "locationPositiveTrainData.txt" \ 16 | --negativefilename "locationNegativeTrainData.txt" \ 17 | --meanstdfilepath ${NORM}".csv" \ 18 | --modelFileNamePath "./result/newmegasolarCNN_NS_72720_" 19 | -------------------------------------------------------------------------------- /torch/train-cnn.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | GPU=1 4 | 5 | MODEL=ishiinet 6 | echo ${MODEL} 7 | 8 | for i in 15 9 | do 10 | th train-cnn.lua \ 11 | -save ${MODEL}_p1n${i} \ 12 | -gpu ${GPU} \ 13 | -trainDataPath ./resource/train \ 14 | -testDataPath ./resource/val \ 15 | -batchNorm \ 16 | -dropout \ 17 | -batchSize 128\ 18 | -batchSizeNM 32 \ 19 | -maxEpoch 3000 \ 20 | -negativeRatio ${i} \ 21 | -saveInterval 10 \ 22 | -optimization SGD \ 23 | -trainnorm ${MODEL}_meanstd.csv \ 24 | | tee log_${MODEL}_p1n${i}.txt 25 | done 26 | -------------------------------------------------------------------------------- /chainer/forwardFunc_IshiiModel.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | import chainer 3 | from chainer import Function, gradient_check, Variable, optimizers, utils 4 | from chainer import Link, Chain, ChainList 5 | 6 | import chainer.functions as F 7 | import chainer.links as L 8 | 9 | def forward(model,x): 10 | 11 | h = model.conv1(x) 12 | h = F.relu(h) 13 | h = model.bn1(h, test=True) 14 | 15 | h = model.conv2(h) 16 | h = F.relu(h) 17 | h = model.bn2(h, test=True) 18 | 19 | h = model.conv3(h) 20 | h = F.relu(h) 21 | h = model.bn2(h, test=True) 22 | 23 | h = model.fcl(h) 24 | y = F.softmax(h) 25 | 26 | model.pred = y 27 | 28 | return y 29 | -------------------------------------------------------------------------------- /chainer/funcLoadBatcFiles.py: -------------------------------------------------------------------------------- 1 | import tifffile as tiff 2 | import numpy as np 3 | 4 | 5 | 6 | def loadbatchfiles(filelist,indexlist,dataMean,dataStd): 7 | 8 | inputchanel = 7 9 | rowsize = 16 10 | colsize = 16 11 | 12 | print dataMean 13 | print dataStd 14 | 15 | statusPrint = False 16 | NumData = len(indexlist) 17 | if NumData > 10000: 18 | statusPrint = True 19 | print('Number of data to be loaded %d' % NumData) 20 | 21 | x_data = np.float32(np.zeros([len(indexlist),inputchanel,rowsize,colsize])) 22 | 23 | for item in range(NumData): 24 | arr = tiff.imread(filelist[indexlist[item]]) 25 | for channel in range(inputchanel): 26 | x_data[item,channel] = np.float32( np.float32(arr[channel]) - np.float32(dataMean[channel] ) / np.float32(dataStd[channel])) 27 | if statusPrint: 28 | if item%10000 == 0: 29 | print('%d of %d data loading complete' % (item,NumData)) 30 | 31 | return x_data 32 | 33 | 34 | -------------------------------------------------------------------------------- /torch/convertGPUToCPU.lua: -------------------------------------------------------------------------------- 1 | 2 | 3 | require 'cunn' 4 | require 'optim' 5 | require 'image' 6 | require 'hdf5' 7 | require 'cutorch' 8 | 9 | -- 10 | cmd = torch.CmdLine() 11 | cmd:text() 12 | cmd:text('CNN TEST') 13 | cmd:text() 14 | cmd:text('Options:') 15 | cmd:option('-network', '', 'reload pretrained network') 16 | cmd:option('-outputNetwork', '', 'name of output network') 17 | cmd:option('-gpu', 1, 'id of gpu to use') 18 | cmd:text() 19 | opt = cmd:parse(arg) 20 | -- 21 | 22 | 23 | cutorch.setDevice(opt.gpu) 24 | 25 | model = torch.load(opt.network) 26 | networkCpu = string.split(opt.network, '.net')[1]..'_cpu.net' 27 | 28 | if torch.typename(model.modules[1].weight) == 'torch.CudaTensor' then 29 | print('Converted network to CPU-mode.') 30 | if opt.outputNetwork == '' then 31 | torch.save(networkCpu, model:double()) 32 | else 33 | torch.save(opt.outputNetwork, model:double()) 34 | end 35 | else 36 | print('Input network is already CPU-mode.') 37 | end 38 | -------------------------------------------------------------------------------- /torch/test-cnn.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | LIST=test_hdf5_list.txt 4 | 5 | 6 | DATA_PATH='./resource' 7 | MODE=ishiinet 8 | 9 | NETWORK_PATH=./${MODEL}_p1n15 10 | NETWORK='cnn_nm_ep2000.net' 11 | OUTPUT_NETWORK='cnn_nm_ep2000_cpu.net' 12 | 13 | th convertGPUToCPU.lua \ 14 | -network ${NETWORK_PATH}/${NETWORK} \ 15 | -outputNetwork ${NETWORK_PATH}/${OUTPUT_NETWORK} \ 16 | -gpu 1 17 | 18 | for THRESHOLD in 0.999 19 | do 20 | cat ${LIST} | while read HDF5 21 | do 22 | th test-cnn-cpu.lua \ 23 | -testDataPath ${DATA_PATH}/test/${HDF5} \ 24 | -testBatchSize 4000 \ 25 | -network ${NETWORK_PATH}/${OUTPUT_NETWORK} \ 26 | -threshold ${THRESHOLD} \ 27 | -meanstd_file ${MODEL}_meanstd.csv \ 28 | | tee ${NETWORK_PATH}/log_th${THRESHOLD}_${HDF5}.txt 29 | done 30 | 31 | for i in ${NETWORK_PATH}/log_th${THRESHOLD}_*${MODE}.hdf5.txt 32 | do 33 | echo ${i} 34 | cat ${i} >> ${NETWORK_PATH}/${MODE}_th${THRESHOLD}_all.txt 35 | done 36 | 37 | python ComputeCM.py \ 38 | --log_file ${NETWORK_PATH}/${MODE}_th${THRESHOLD}_all.txt \ 39 | > ${NETWORK_PATH}/${MODE}_th${THRESHOLD}_all_cm.txt 40 | 41 | done 42 | 43 | 44 | 45 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 |

MUSIC for P3 dataset Terms of Use

2 |

October 20 , 2017

3 | 4 | The users of the MUSIC for P3 dataset must abide by the following Terms of Use: 5 | 6 | 7 | 1. When publishing results that use this dataset, the users shall comply with the following matters: 8 | * The users must cite the URL of the MUSIC for P3 dataset (i.e., https://github.com/gistairc/MUSIC4P3) in their publication. 9 | 10 | 11 | 2. By downloading the dataset, the users are regarded as having agreed to the Terms of Use. 12 | 13 | 3. The software is provided "as is", without warranty of any kind, express or implined, including but not limited to the warranties of merchantabilty, fitness for a particular purpose and noninfringement. 14 | In no event shall the authors or copyright holders be liable for any claim. damages or other liability, whether in an action of contract, tort or otherwise, arising from, out of or in connection with the software or the use or other dealings in the software. 15 | 16 | MUSIC for P3 dataset is based on results obtained from a project commissioned by the New Energy and Industrial Technology Development Organization (NEDO). 17 | 18 | National Institute of Advanced Industrial Science and Technology 19 | 20 | Artificial Intelligence Research Center, Japan 21 | -------------------------------------------------------------------------------- /chainer/megasolar_model_ishii.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | import chainer 3 | from chainer import Function, gradient_check, Variable, optimizers, utils 4 | from chainer import Link, Chain, ChainList 5 | 6 | import chainer.functions as F 7 | import chainer.links as L 8 | 9 | class MegaSolarBN(chainer.Chain): 10 | insize = 16 11 | def __init__(self): 12 | super(MegaSolarBN, self).__init__( 13 | conv1=L.Convolution2D(7, 32, 3, stride=1,pad=0), 14 | bn1=L.BatchNormalization(32), 15 | conv2=L.Convolution2D(32, 32, 3, stride=1,pad=0), 16 | bn2=L.BatchNormalization(32), 17 | conv3=L.Convolution2D(32, 32, 3, stride=1,pad=0), 18 | bn3=L.BatchNormalization(32), 19 | fcl=L.Linear(32*10*10, 2), 20 | ) 21 | self.train = True 22 | 23 | def __call__(self, x,t): 24 | h = self.conv1(x) 25 | h = F.relu(h) 26 | h = self.bn1(h, test=not self.train) 27 | 28 | h = self.conv2(h) 29 | h = F.relu(h) 30 | h = self.bn2(h, test=not self.train) 31 | 32 | h = self.conv3(h) 33 | h = F.relu(h) 34 | h = self.bn3(h, test=not self.train) 35 | 36 | h = F.dropout(h, train=self.train) 37 | 38 | h = self.fcl(h) 39 | 40 | 41 | self.pred = F.softmax(h) 42 | self.loss = F.softmax_cross_entropy(h, t) 43 | self.accuracy = F.accuracy(self.pred, t) 44 | if self.train: 45 | return self.loss 46 | else: 47 | return self.pred 48 | 49 | 50 | -------------------------------------------------------------------------------- /torch/test_hdf5_list.txt: -------------------------------------------------------------------------------- 1 | LC81050302016143LGN00_P12_N40385.hdf5 2 | LC81090342016155LGN00_P64_N40079.hdf5 3 | LC81060292016182LGN00_P22_N40354.hdf5 4 | LC81090352016139LGN00_P274_N39940.hdf5 5 | LC81060302016134LGN00_P51_N40247.hdf5 6 | LC81090362016139LGN00_P449_N39634.hdf5 7 | LC81070292016189LGN00_P24_N40458.hdf5 8 | LC81100342016290LGN00_P7_N40240.hdf5 9 | LC81070302015266LGN00_P210_N40187.hdf5 10 | LC81100352016082LGN00_P77_N40246.hdf5 11 | LC81070312016237LGN00_P20_N40400.hdf5 12 | LC81100362016082LGN00_P529_N39523.hdf5 13 | LC81070322016221LGN00_P60_N40343.hdf5 14 | LC81100372016082LGN00_P31_N40307.hdf5 15 | LC81070332015346LGN00_P95_N40248.hdf5 16 | LC81110352015294LGN00_P72_N40332.hdf5 17 | LC81070342016077LGN00_P244_N39991.hdf5 18 | LC81110362016121LGN00_P251_N39996.hdf5 19 | LC81070352016077LGN00_P862_N38979.hdf5 20 | LC81110372016121LGN00_P42_N40336.hdf5 21 | LC81070362016189LGN00_P340_N39934.hdf5 22 | LC81120362016144LGN00_P162_N40098.hdf5 23 | LC81080302015145LGN00_P197_N40182.hdf5 24 | LC81120372016224LGN00_P173_N39953.hdf5 25 | LC81080312015193LGN00_P25_N40387.hdf5 26 | LC81120382016080LGN00_P95_N40020.hdf5 27 | LC81080322016116LGN00_P57_N40330.hdf5 28 | LC81130372016151LGN00_P213_N39808.hdf5 29 | LC81080332016244LGN00_P43_N40330.hdf5 30 | LC81130382015308LGN00_P51_N39994.hdf5 31 | LC81080342016244LGN00_P166_N40079.hdf5 32 | LC81130422015260LGN00_P40_N40142.hdf5 33 | LC81080352016004LGN00_P753_N39254.hdf5 34 | LC81140372016062LGN00_P2_N40385.hdf5 35 | LC81080362016004LGN00_P238_N40058.hdf5 36 | -------------------------------------------------------------------------------- /torch/ComputeCM.py: -------------------------------------------------------------------------------- 1 | 2 | # coding: utf-8 3 | 4 | import string 5 | import numpy as np 6 | import argparse 7 | 8 | parser = argparse.ArgumentParser(description='') 9 | parser.add_argument('--log_file', default='') 10 | args = parser.parse_args() 11 | 12 | 13 | f = open(args.log_file, 'r') 14 | str_list = [] 15 | for line in f: 16 | str_list.append(line) 17 | f.close() 18 | 19 | 20 | def get_cm_value(str_line): 21 | tmp_str = [] 22 | for i in range(len(str_line)): 23 | if str_line[i] != '': 24 | tmp_str.append(str_line[i]) 25 | 26 | return int(tmp_str[1]), int(string.split(tmp_str[2], ']')[0]) 27 | 28 | result_list = [] 29 | for i in range(len(str_list)): 30 | tmp_line = string.split(str_list[i],' ') 31 | if ('test' in tmp_line) and ('all' in tmp_line): 32 | result_list.append([i, 33 | get_cm_value(string.split(str_list[i+2])), 34 | get_cm_value(string.split(str_list[i+3]))]) 35 | 36 | confusion = [[0,0],[0,0]] 37 | 38 | IOU_list = [] 39 | for i in range(len(result_list)): 40 | tp = result_list[i][1][0] 41 | fn = result_list[i][1][1] 42 | fp = result_list[i][2][0] 43 | tn = result_list[i][2][1] 44 | for j in [0,1]: 45 | for k in [0,1]: 46 | confusion[j][k] = confusion[j][k] + result_list[i][j+1][k] 47 | 48 | iou = float(confusion[0][0])/float(confusion[0][0]+confusion[0][1]+confusion[1][0]) 49 | recall = float(confusion[0][0])/float(confusion[0][0]+confusion[0][1]) 50 | precision = float(confusion[0][0])/float(confusion[0][0]+confusion[1][0]) 51 | 52 | 53 | print 'Confusion Matrix:' 54 | print confusion 55 | 56 | print 'IOU:'+str(iou) 57 | print 'Precision:'+str(precision) 58 | print 'Recall:'+str(recall) 59 | 60 | -------------------------------------------------------------------------------- /chainer/funcMakeNormFiles.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | #bin/python 3 | import tifffile as tiff 4 | import numpy as np 5 | 6 | import sys 7 | import argparse 8 | 9 | import funcReadLocations as readtxt 10 | 11 | import sys 12 | 13 | parser = argparse.ArgumentParser(description='MakeNormariztionFiles(mean std)') 14 | parser.add_argument('--filelocationspath', default='resource') 15 | parser.add_argument('--positivefilename', default='locationPositiveTrainData.txt') 16 | parser.add_argument('--negativefilename', default='locationNegativeTrainData.txt') 17 | parser.add_argument('--meanstdfilepath', default='meanstd') 18 | args = parser.parse_args() 19 | 20 | def mathdatameanstd(pfile,pindex,nfile,nindex,inputchanel): 21 | 22 | 23 | image=[] 24 | for channel in range(inputchanel): 25 | band=[] 26 | image.append(band) 27 | pNum= len(pindex) 28 | nNum= len(nindex) 29 | for NumData in pNum,nNum: 30 | if NumData is pNum: 31 | filelist = pfile 32 | index = pindex 33 | elif NumData is nNum: 34 | filelist = nfile 35 | index = nindex 36 | for item in range(NumData): 37 | arr = tiff.imread(filelist[index[item]]) 38 | for channel in range(inputchanel): 39 | image[channel].append(np.float32(arr[channel])) 40 | 41 | mean=[] 42 | std=[] 43 | for channel in range(inputchanel): 44 | data = np.array(image[channel]) 45 | mean.append(np.mean(data)) 46 | std.append(np.std(data)) 47 | 48 | return mean,std 49 | 50 | if __name__ == "__main__": 51 | 52 | inputchanel=7 53 | 54 | pfilename = args.filelocationspath + "/"+args.positivefilename 55 | trainPosLocations = readtxt.ReadLocations(pfilename); 56 | nfilename = args.filelocationspath + "/"+args.negativefilename 57 | trainNegLocations = readtxt.ReadLocations(nfilename); 58 | 59 | negdatasize = len(trainNegLocations) 60 | posdatasize = len(trainPosLocations) 61 | 62 | mean,std = mathdatameanstd(trainPosLocations,range(posdatasize),trainNegLocations,range(negdatasize),inputchanel) 63 | 64 | file1 = open(str(args.meanstdfilepath)+".csv",'a') 65 | 66 | for channel in range(inputchanel): 67 | file1.write(str(mean[channel])+","+str(std[channel])+'\n') 68 | file1.close() 69 | 70 | 71 | -------------------------------------------------------------------------------- /chainer/trainMegaSolarData_IshiiModel_LoadAll.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | #bin/python 3 | 4 | import numpy as np 5 | import chainer 6 | from chainer import Function, gradient_check, Variable, optimizers, utils 7 | from chainer import Link, Chain, ChainList 8 | from chainer import serializers 9 | from chainer import cuda 10 | from chainer import computational_graph 11 | 12 | 13 | import funcReadLocations as readtxt 14 | import megasolar_model_ishii as cnnmodel 15 | import funcLoadBatcFiles as bl 16 | 17 | import chainer.functions as F 18 | import chainer.links as L 19 | 20 | import os 21 | import csv 22 | import argparse 23 | 24 | 25 | 26 | parser = argparse.ArgumentParser(description='train') 27 | parser.add_argument('--filelocationspath', default='resource') 28 | parser.add_argument('--trainDataPath', default='train') 29 | parser.add_argument('--positivefilename', default='locationPositiveTrainData.txt') 30 | parser.add_argument('--negativefilename', default='locationNegativeTrainData.txt') 31 | parser.add_argument('--meanstdfilepath', default='meanstd') 32 | parser.add_argument('--modelFileNamePath', default='') 33 | args = parser.parse_args() 34 | 35 | def get_coordinate_from_csv(csv_file): 36 | print csv_file 37 | f = open(str(csv_file),'r') 38 | file_list = csv.reader(f) 39 | cor_list = [] 40 | mean=[] 41 | std=[] 42 | for row in file_list: 43 | cor_list.append(row) 44 | f.close() 45 | for i in range(len(cor_list)): 46 | mean.append(np.float32(cor_list[i][0])) 47 | std.append(np.float32(cor_list[i][1])) 48 | return mean,std 49 | 50 | def trainMegaSolarCNN(): 51 | 52 | 53 | 54 | # paths in the local machine 55 | filelocationspath = args.filelocationspath 56 | 57 | 58 | filename = filelocationspath +"/"+args.positivefilename 59 | trainPosLocations = readtxt.ReadLocations(filename); 60 | filename = filelocationspath +"/"+args.negativefilename 61 | trainNegLocations = readtxt.ReadLocations(filename); 62 | 63 | 64 | model = cnnmodel.MegaSolarBN() 65 | optimizer = optimizers.MomentumSGD(lr=0.001, momentum=0.9) 66 | optimizer.setup(model) 67 | model.to_gpu() 68 | 69 | negbatchsize = 120 70 | posbatchsize = 8 71 | negdatasize = len(trainNegLocations) 72 | posdatasize = len(trainPosLocations) 73 | datamean=[] 74 | datastd=[] 75 | datamean,datastd=get_coordinate_from_csv(args.meanstdfilepath) 76 | 77 | x_train_all_positive = bl.loadbatchfiles(trainPosLocations,range(posdatasize),datamean,datastd) 78 | x_train_all_negative = bl.loadbatchfiles(trainNegLocations,range(negdatasize),datamean,datastd) 79 | 80 | 81 | NumNegTrainData = 72720 82 | 83 | print('Number of Negative samples %d' % (negdatasize)) 84 | print('Number of Positive samples %d' % (posdatasize)) 85 | print('----------------------------------------------------------') 86 | print('Number of Negative samples at each epoch %d' % (NumNegTrainData)) 87 | print('Number of Positive samples at each epoch %d' % (posdatasize)) 88 | print('----------------------------------------------------------') 89 | 90 | batchlooplen = len(range(0, NumNegTrainData, negbatchsize)); 91 | datasize = batchlooplen*(posbatchsize+negbatchsize) 92 | NUMEPOCH = 5000 93 | for epoch in range(NUMEPOCH): 94 | print('epoch %d' % epoch) 95 | sum_loss = 0 96 | sum_accuracy = 0 97 | mean_loss = np.zeros(NUMEPOCH) 98 | mean_accuracy = np.zeros(NUMEPOCH) 99 | negindexes = np.random.permutation(negdatasize) 100 | posindexes = np.random.permutation(posdatasize) 101 | posindexes = np.tile(posindexes,(10*8)) 102 | for i in range(0, NumNegTrainData, negbatchsize): 103 | 104 | 105 | x_train_positive = x_train_all_positive[posindexes[(i/negbatchsize)*posbatchsize:(i/negbatchsize)*posbatchsize+posbatchsize]] 106 | y_train_positive = np.int32(np.ones(posbatchsize)) 107 | 108 | x_train_negative = x_train_all_negative[negindexes[i:i+negbatchsize]] 109 | y_train_negative = np.int32(np.zeros(negbatchsize)) 110 | 111 | x_train = np.append(x_train_positive,x_train_negative,axis=0) 112 | y_train = np.append(y_train_positive,y_train_negative) 113 | 114 | indexes = np.random.permutation(posbatchsize+negbatchsize) 115 | x = Variable(cuda.to_gpu(x_train[indexes])) 116 | t = Variable(cuda.to_gpu(y_train[indexes])) 117 | 118 | 119 | optimizer.update(model,x,t) 120 | loss = model(x, t) 121 | 122 | sum_loss += loss.data * (posbatchsize+negbatchsize) 123 | sum_accuracy += model.accuracy.data * (posbatchsize+negbatchsize) 124 | 125 | print "EPOCH MEAN TRAIN LOSS and ACCURACY VALUES: " 126 | mean_loss[epoch] = sum_loss / datasize 127 | mean_accuracy[epoch] = sum_accuracy / datasize 128 | print mean_loss[epoch] 129 | print mean_accuracy[epoch] 130 | 131 | modelFileName = str(args.modelFileNamePath)+str(epoch)+"_iteration.model" 132 | stateFileName = str(args.modelFileNamePath)+str(epoch)+"_iteration.state" 133 | serializers.save_npz(modelFileName, model) 134 | serializers.save_npz(stateFileName, optimizer) 135 | 136 | return model,optimizer,loss,mean_loss,mean_accuracy 137 | 138 | if __name__ == "__main__": 139 | model,optimizer,loss,mean_loss,mean_accuracy=trainMegaSolarCNN() 140 | -------------------------------------------------------------------------------- /chainer/testMegaSolarData_IshiiModel_LoadAll_outIoU.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | #bin/python 3 | 4 | import numpy as np 5 | import chainer 6 | from chainer import Function, gradient_check, Variable, optimizers, utils 7 | from chainer import Link, Chain, ChainList 8 | from chainer import serializers 9 | from chainer import cuda 10 | from chainer import computational_graph 11 | import libtiff 12 | 13 | import funcReadLocations as readtxt 14 | import megasolar_model_ishii as cnnmodel 15 | import forwardFunc_IshiiModel as cnnForward 16 | import funcLoadBatcFiles as bl 17 | 18 | 19 | import chainer.functions as F 20 | import chainer.links as L 21 | 22 | import os 23 | import csv 24 | import argparse 25 | 26 | 27 | parser = argparse.ArgumentParser(description='train') 28 | parser.add_argument('--filelocationspath', default='resource') 29 | parser.add_argument('--testDataPath', default='test') 30 | parser.add_argument('--positivefilename', default='locationPositiveTrainData.txt') 31 | parser.add_argument('--negativefilename', default='locationNegativeTrainData.txt') 32 | parser.add_argument('--meanstdfilepath', default='meanstd') 33 | parser.add_argument('--modelFileNamePath', default='') 34 | args = parser.parse_args() 35 | 36 | def get_coordinate_from_csv(csv_file): 37 | print csv_file 38 | f = open(str(csv_file),'r') 39 | file_list = csv.reader(f) 40 | cor_list = [] 41 | mean=[] 42 | std=[] 43 | for row in file_list: 44 | cor_list.append(row) 45 | f.close() 46 | for i in range(len(cor_list)): 47 | mean.append(np.float32(cor_list[i][0])) 48 | std.append(np.float32(cor_list[i][1])) 49 | return mean,std 50 | 51 | def testMegaSolarCNN(): 52 | 53 | 54 | 55 | # paths in the local machine 56 | filelocationspath = args.filelocationspath 57 | 58 | filename = filelocationspath +"/"+args.positivefilename 59 | testPosLocations = readtxt.ReadLocations(filename); 60 | filename = filelocationspath +"/"+args.negativefilename 61 | testNegLocations = readtxt.ReadLocations(filename); 62 | 63 | 64 | negbatchsize = 500 65 | posdatasize = len(testPosLocations) 66 | 67 | negdatasize_part1 = len(testNegLocations) - len(testNegLocations)%negbatchsize 68 | negdatasize = len(testNegLocations) 69 | 70 | 71 | datamean=[] 72 | datastd=[] 73 | datamean,datastd=get_coordinate_from_csv(args.meanstdfilepath) 74 | 75 | 76 | x_test_all_positive = bl.loadbatchfiles(testPosLocations,range(posdatasize),datamean,datastd) 77 | x_test_all_negative = bl.loadbatchfiles(testNegLocations,range(negdatasize),datamean,datastd) 78 | 79 | print('Number of Negative samples %d' % (negdatasize)) 80 | print('Number of Positive samples %d' % (posdatasize)) 81 | print('----------------------------------------------------------') 82 | 83 | 84 | model = cnnmodel.MegaSolarBN() 85 | 86 | MAX_EPOCH = 5000 87 | accuracy_pos = [] 88 | accuracy_neg = [] 89 | 90 | iou_all = [] 91 | 92 | my_pos_thresh = 0.999 93 | 94 | for epoch in range(0,MAX_EPOCH, 1): 95 | print('EPOCH %d' % epoch) 96 | modelFileNamePath=str(args.modelFileNamePath) 97 | modelFileName = modelFileNamePath+str(epoch)+"_iteration.model" 98 | serializers.load_npz(modelFileName, model) 99 | model.to_gpu() 100 | 101 | model.train = False 102 | print "___________________________________________" 103 | print "Epoch MEAN TEST ACCURACY Positive Samples: " 104 | x_test_positive = x_test_all_positive 105 | y_test_positive = np.int32(np.ones(len(testPosLocations))) 106 | x = Variable(cuda.to_gpu(x_test_positive)) 107 | t = Variable(cuda.to_gpu(y_test_positive)) 108 | respos = cnnForward.forward(model,x) 109 | cnn_prediction = np.float32(cuda.to_cpu(respos.data[:,1]) > my_pos_thresh) 110 | cnn_acc = cnn_prediction.sum()/posdatasize 111 | accuracy_pos = np.append(accuracy_pos,cnn_acc) 112 | print cnn_acc 113 | x_pos_test = x 114 | t_pos_test = t 115 | print "___________________________________________" 116 | print "Epoch MEAN TEST ACCURACY Negative Samples: " 117 | 118 | sum_accuracy = 0 119 | for ti in range(0,negdatasize_part1, negbatchsize): 120 | 121 | x_test_negative = x_test_all_negative[ti:ti+negbatchsize] 122 | y_test_negative = np.int32(np.zeros(negbatchsize)) 123 | x = Variable(cuda.to_gpu(x_test_negative)) 124 | t = Variable(cuda.to_gpu(y_test_negative)) 125 | resneg = cnnForward.forward(model,x) 126 | cnn_prediction = np.float32(cuda.to_cpu(resneg.data[:,1]) <= my_pos_thresh) 127 | cnn_acc = cnn_prediction.sum()/negbatchsize 128 | sum_accuracy += cnn_acc * (negbatchsize) 129 | 130 | 131 | x_test_negative = x_test_all_negative[negdatasize_part1:negdatasize] 132 | y_test_negative = np.int32(np.zeros(len(range(negdatasize_part1,negdatasize)))) 133 | x = Variable(cuda.to_gpu(x_test_negative)) 134 | t = Variable(cuda.to_gpu(y_test_negative)) 135 | resneg = cnnForward.forward(model,x) 136 | cnn_prediction = np.float32(cuda.to_cpu(resneg.data[:,1]) <= my_pos_thresh) 137 | cnn_acc = cnn_prediction.sum()/len(y_test_negative) 138 | sum_accuracy += cnn_acc * len(y_test_negative) 139 | test_negative_accuracy = sum_accuracy / negdatasize 140 | 141 | accuracy_neg = np.append(accuracy_neg,test_negative_accuracy) 142 | 143 | print test_negative_accuracy 144 | x_neg_test = x 145 | t_neg_test = t 146 | TP = np.round(accuracy_pos[-1]*posdatasize) 147 | FN = posdatasize - TP 148 | FP = np.round(negdatasize - accuracy_neg[-1]*negdatasize) 149 | TN = negdatasize - FP 150 | 151 | iou_all = np.append(iou_all,TP/(TP+FN+FP)) 152 | print "___________________________________________" 153 | print "Epoch MEAN TEST Intersection over Union: " 154 | print iou_all[-1] 155 | print "TP:"+str(TP)+" FN:"+str(FN) 156 | print "FP:"+str(FP)+" TN:"+str(TN) 157 | 158 | return model, iou_all, x_pos_test, t_pos_test, x_neg_test, t_neg_test 159 | 160 | if __name__ == "__main__": 161 | model, iou_all, x_pos_test, t_pos_test, x_neg_test, t_neg_test=testMegaSolarCNN() 162 | -------------------------------------------------------------------------------- /torch/test-cnn-cpu.lua: -------------------------------------------------------------------------------- 1 | 2 | require 'nn' 3 | require 'optim' 4 | require 'image' 5 | require 'hdf5' 6 | require 'torch' 7 | require 'lfs' 8 | 9 | -- 10 | cmd = torch.CmdLine() 11 | cmd:text() 12 | cmd:text('CNN TEST') 13 | cmd:text() 14 | cmd:text('Options:') 15 | cmd:option('-network', '', 'reload pretrained network') 16 | cmd:option('-inputChannel', 7, 'nb of input channels') 17 | cmd:option('-testBatchSize', 10000, 'test batch size') 18 | cmd:option('-trainDataPath', '', 'path of hdf5 dataset for training') 19 | cmd:option('-testDataPath', '', 'path of hdf5 dataset for test') 20 | cmd:option('-threshold', 0.5, 'threshold of softmax') 21 | cmd:option('-meanstd_file', 'mean_stdv.csv', 'training mean and stdv data') 22 | cmd:text() 23 | opt = cmd:parse(arg) 24 | -- 25 | 26 | model = torch.load(opt.network):double() 27 | criterion = nn.ClassNLLCriterion():double() 28 | 29 | function split(str, delim) 30 | -- Eliminate bad cases... 31 | if string.find(str, delim) == nil then 32 | return { str } 33 | end 34 | 35 | local result = {} 36 | local pat = "(.-)" .. delim .. "()" 37 | local lastPos 38 | for part, pos in string.gfind(str, pat) do 39 | table.insert(result, part) 40 | lastPos = pos 41 | end 42 | table.insert(result, string.sub(str, lastPos)) 43 | return result 44 | end 45 | 46 | file = io.open(opt.meanstd_file,"r") 47 | dataMean={} 48 | dataStd ={} 49 | for line in file:lines() do 50 | local datas = split(line, ",") 51 | table.insert(dataMean,datas[1]) 52 | table.insert(dataStd,datas[2]) 53 | end 54 | 55 | function getFileList(dirPath, ext) 56 | list = {} 57 | for file in lfs.dir(dirPath) do 58 | if string.find(file, ext..'$') then 59 | table.insert(list,file) 60 | end 61 | end 62 | return list 63 | end 64 | 65 | -- this matrix records the current confusion across classes 66 | classes = {'positive', 'negative'} 67 | confusion = optim.ConfusionMatrix(classes) 68 | 69 | probList = {} 70 | labelList = {} 71 | 72 | 73 | -- import test data 74 | if lfs.attributes(opt.testDataPath)['mode'] == 'file' then 75 | -- import training data 76 | print("Import test data...") 77 | print('Importing:'..opt.testDataPath) 78 | myFile = hdf5.open(opt.testDataPath,'r') 79 | testData = myFile:read(''):all() 80 | myFile:close() 81 | labelNum = testData.label:size()[1] 82 | 83 | for i = 1,labelNum do 84 | if testData.label[i] == 0 then 85 | testData.label[i] = 1 86 | else 87 | testData.label[i] = 2 88 | end 89 | end 90 | 91 | numAllTestData = labelNum 92 | 93 | testIdx = testData.idx 94 | 95 | 96 | else 97 | error("invalid dataset") 98 | end 99 | 100 | for t=1,opt.inputChannel do 101 | testData.data[{ {},t,{},{} }]:add(-dataMean[t]) 102 | testData.data[{ {},t,{},{} }]:div(dataStd[t]) 103 | print(t.."ch mean:"..dataMean[t].." stdv:"..dataStd[t]) 104 | end 105 | 106 | testData = { 107 | data = testData.data, 108 | labels = testData.label, 109 | size = function() return numAllTestData end 110 | } 111 | 112 | 113 | -- test function 114 | function testRapid(dataset) 115 | 116 | model:evaluate() 117 | 118 | -- local vars 119 | local testError = 0 120 | local time = sys.clock() 121 | 122 | if average then 123 | cachedparams = parameters:clone() 124 | parameters:copy(average) 125 | end 126 | 127 | 128 | local numErr = 0 129 | 130 | for t = 1,dataset:size(),opt.testBatchSize do 131 | 132 | local batchStart = t 133 | local batchEnd = math.min(t+opt.testBatchSize-1,dataset:size()) 134 | local inputs = dataset.data[{{batchStart,batchEnd},{},{},{}}] 135 | local targets = dataset.labels[{{batchStart,batchEnd}}] 136 | 137 | local preds = model:forward(inputs) 138 | for i = 1, math.min(opt.testBatchSize,dataset:size()-t+1) do 139 | confusion:add(preds[i], targets[i]) 140 | table.insert(probList, preds[i]:clone()) 141 | table.insert(labelList, targets[i]) 142 | end 143 | 144 | err = criterion:forward(preds, targets) 145 | testError = testError + err 146 | numErr = numErr + 1 147 | end 148 | 149 | -- timing 150 | time = sys.clock() - time 151 | allTime = time 152 | time = time / dataset:size() 153 | print(" time to test 1 sample = " .. (time*1000) .. 'ms') 154 | print(" time to test all samples = " .. (allTime*1000) .. 'ms') 155 | 156 | -- testing error estimation 157 | testError = testError / numErr 158 | 159 | local testAccuracy = confusion.totalValid * 100 160 | confusion:zero() 161 | 162 | -- averaged param use? 163 | if average then 164 | -- restore parameters 165 | parameters:copy(cachedparams) 166 | end 167 | 168 | return testAccuracy, testError 169 | end 170 | 171 | testAcc, testErr = testRapid(testData) 172 | 173 | 174 | falsePositiveList = {} 175 | falseNegativeList = {} 176 | truePositiveList = {} 177 | 178 | splitTestDataPath = string.split(opt.testDataPath,'/') 179 | hdf5Name = string.split(splitTestDataPath[#splitTestDataPath],'.hdf5') 180 | csvDataPath = opt.threshold..'_'..hdf5Name[1]..'.csv' 181 | lfs.mkdir(hdf5Name[1]) 182 | 183 | confusion = {{0,0},{0,0}} 184 | 185 | for i=1, #probList do 186 | if labelList[i] == 1 then 187 | if probList[i][1] >= math.log(opt.threshold) then 188 | table.insert(truePositiveList,{probList[i][1],testIdx[i][1],testIdx[i][2]}) 189 | confusion[1][1] = confusion[1][1] + 1 190 | else 191 | table.insert(falseNegativeList,{probList[i][1],testIdx[i][1],testIdx[i][2]}) 192 | confusion[1][2] = confusion[1][2] + 1 193 | end 194 | else 195 | if probList[i][1] >= math.log(opt.threshold) then 196 | table.insert(falsePositiveList,{probList[i][1],testIdx[i][1],testIdx[i][2]}) 197 | confusion[2][1] = confusion[2][1] + 1 198 | else 199 | confusion[2][2] = confusion[2][2] + 1 200 | end 201 | end 202 | end 203 | 204 | str = {'ConfusionMatrix:\n'} 205 | table.insert(str, "[[ "..confusion[1][1].." "..confusion[1][2].."] \n") 206 | table.insert(str, " [ "..confusion[2][1].." "..confusion[2][2].."]] \n") 207 | print(table.concat(str)) 208 | 209 | 210 | f = io.open(hdf5Name[1].."/FP_"..csvDataPath,"w") 211 | for i=1, #falsePositiveList do 212 | f:write(falsePositiveList[i][1]..","..falsePositiveList[i][2]..","..falsePositiveList[i][3].."\n") 213 | end 214 | f:close() 215 | 216 | f = io.open(hdf5Name[1].."/TP_"..csvDataPath,"w") 217 | for i=1, #truePositiveList do 218 | f:write(truePositiveList[i][1]..","..truePositiveList[i][2]..","..truePositiveList[i][3].."\n") 219 | end 220 | f:close() 221 | 222 | f = io.open(hdf5Name[1].."/FN_"..csvDataPath,"w") 223 | for i=1, #falseNegativeList do 224 | f:write(falseNegativeList[i][1]..","..falseNegativeList[i][2]..","..falseNegativeList[i][3].."\n") 225 | end 226 | f:close() 227 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # MUSIC for P3 dataset 2 | 3 | ## Overview 4 | 5 | The number of photovoltaic power plants is growing so rapidly that we must rely on satellitel observations and efficient machine learning methods for the global monitoring. **MUSIC** for P3 (**P**hotovoltaic **P**ower **P**lants) is a training and validation dataset generated by [AIRC/AIST](http://www.airc.aist.go.jp/en/) to support such a global survey of photovoltaic power plants. 6 | 7 | 8 | We have picked up the photovoltaic power plants listed on [the website of Electrical Japan](http://agora.ex.nii.ac.jp/earthquake/201103-eastjapan/energy/electrical-japan/type/8.html.ja) only if they generated more than 5MW electricity and the construction had been completed by 2015. The multiband satellite images of these target areas, taken by [Landsat-8](https://landsat.usgs.gov/landsat-8), were cropped into a 16 × 16 grid covering a 480 × 480 meter area as shown below. 9 | 10 | ![fig:MUSIC4P3 image patch example](https://github.com/gistairc/MUSIC4P3/blob/master/fig.jpg "megasolar image patch example") 11 | 12 | 13 | These patch images are classified as “positives” if the solar panels cover more than 20% of the total areas, while patches with no solar panels are classified as “negatives”. The rest with the intermediate coverage (0~20%) were neither “positives” and “negatives”. 14 | 15 | 16 | You can download the **MUSIC** for P3 dataset with two different format ([HDF5](https://github.com/gistairc/MUSIC4P3#hdf5) and [GeoTiff](https://github.com/gistairc/MUSIC4P3#tiff)) along with the source code for the detection and classification. More detailed exaplanations can be found in the following papers. 17 | 18 | [1] *Tomohiro Ishii, Edgar Simo-Serra, Satoshi Iizuka, Yoshihiko Mochizuki, Akihiro Sugimoto, Ryosuke Nakamura, Hiroshi Ishikawa ,"Detection by Classification of Buildings in Multispectral Satellite Imagery," ICPR 2016.* (http://www.f.waseda.jp/hfs/IshiiICPR2016.pdf) 19 | 20 | [2] *Nevrez Imamoglu, Motoki Kimura, Hiroki Miyamoto, Aito Fujita, Ryosuke Nakamura,"Solar Power Plant Detection on Multi-Spectral Satellite Imagery using Weakly-Supervised CNN with Feedback Features and m-PCNN Fusion," BMVC 2017.* (https://arxiv.org/abs/1704.06410) 21 | 22 | ##### Updated dataset (V2) 23 | It should be noted here that the "negatives" in the initial dataset (V1) is contaminated by small-scale photovoltaic power plants not included in the [original inventory](http://agora.ex.nii.ac.jp/earthquake/201103-eastjapan/energy/electrical-japan/type/8.html.ja). After publishing these papers, we have checked all the false positives through in-site survey or high-resolution imagery. In fact, some of "false positives" are found to be relatievely small photovoltaic power plants unlisted in the original database. Therefore, we've produced updated V2 dataset by correcting these misclassification. The performance of our previous works was estimated with V1 dataset, but more accurate estimate can be achieved by using this updated dataset V2. 24 | 25 | 26 | 27 | ## Download 28 | **IMPORTANT** -- Please read the [Terms of Use](https://github.com/gistairc/MUSIC4P3/blob/master/LICENSE.md) before downloading the MUSIC4P3 dataset. 29 | 30 | 31 | #### hdf5 32 | HDF5 version (for using [Torch](https://github.com/gistairc/MUSIC4P3#torch-ver) version code) of the dataset can be downloaded from [here](http://data.airc.aist.go.jp/MUSIC4P3dataset/MUSIC4P3data_hdf.zip) (4.6GB) . 33 | Or type the following in the terminal. 34 | 35 | ``` 36 | $ wget http://data.airc.aist.go.jp/MUSIC4P3dataset/MUSIC4P3data_hdf.zip 37 | $ unzip MUSIC4P3data_hdf.zip 38 | ``` 39 | 40 | The directory configuration in the unzipped folder: 41 | ``` 42 | ./torch/resource/ 43 | train/ 44 | LC81060302015147LGN00.hdf5 45 | LC81070302015266LGN00.hdf5 ... 46 | val/ 47 | LC81060302015147LGN00.hdf5 48 | LC81070302015266LGN00.hdf5 ... 49 | test/ 50 | LC81060302015147LGN00.hdf5 51 | LC81070302015266LGN00.hdf5 ... 52 | 53 | 54 | ``` 55 | 56 | 57 | [V2](https://github.com/gistairc/MUSIC4P3/blob/master/README.md#update-data-v2) dataset is [here](http://data.airc.aist.go.jp/MUSIC4P3dataset/MUSIC4P3data_hdf_v2.zip). 58 | 59 | 60 | #### tiff 61 | Tiff version (for using [Chainer](https://github.com/gistairc/MUSIC4P3#chainer-ver) version code) of the dataset can be downloaded from [here](http://data.airc.aist.go.jp/MUSIC4P3dataset/MUSIC4P3data_tiff.zip) (4.2GB) . 62 | Or type the following in the terminal. 63 | ``` 64 | $ wget http://data.airc.aist.go.jp/MUSIC4P3dataset/MUSIC4P3data_tiff.zip 65 | $ unzip MUSIC4P3data_tiff.zip 66 | ``` 67 | The directory configuration in the unzipped files is as follows: 68 | ``` 69 | ./chainer/resource/ 70 | train/ 71 | positive/ 72 | LC81060302015147LGN00_274_300_2064.tiff 73 | LC81060302015147LGN00_274_300_13289.tiff ... 74 | negative/ 75 | LC81060302015147LGN00_100_105.tiff 76 | LC81060302015147LGN00_100_114.tiff ... 77 | val/ 78 | positive/ 79 | LC81070352015298LGN00_206_266.tiff 80 | LC81070352015298LGN00_374_160.tiff ... 81 | negative/ 82 | LC81060302015147LGN00_2_86.tiff 83 | LC81060302015147LGN00_4_88.tiff ... 84 | test/ 85 | positive/ 86 | LC81060302015147LGN00_79_323.tiff 87 | LC81060302015147LGN00_80_323.tiff ... 88 | negative/ 89 | LC81060302015147LGN00_100_100.tiff 90 | LC81060302015147LGN00_100_101.tiff ... 91 | ``` 92 | 93 | [V2](https://github.com/gistairc/MUSIC4P3/blob/master/README.md#update-data-v2) dataset is [here](http://data.airc.aist.go.jp/MUSIC4P3dataset/MUSIC4P3data_tiff_v2.zip). 94 | 95 | ## Source 96 | ### Torch ver 97 | #### Requierment 98 | * torch7 99 | For torch installition see http://torch.ch/ 100 | * torch_toolbox 101 | Download from https://github.com/e-lab/torch-toolbox 102 | Unzip and put the it on the same directory. 103 | ``` 104 | FOR EXAMPLE 105 | torch/ 106 | resource 107 | torch-toolbox 108 | train-cnn.lua 109 | train-cnn.sh 110 | test-cnn.lua 111 | test-cnn.sh ... 112 | ``` 113 | 114 | #### Training 115 | For trainging, type the followingin the terminal. 116 | 117 | ``` 118 | $ sh train-cnn.sh 119 | ``` 120 | Models are saved to "./ishiinet_p1n15/cnn_nm_epXXXX.net" (the output file name is specified in train-cnn.sh) . 121 | 122 | #### Testing 123 | 124 | Type the following in the terminal. 125 | 126 | ``` 127 | $ sh test-cnn.sh 128 | ``` 129 | Result file are saved to "./ishiinet_p1n15/test_th0.999_all_cm.txt". 130 | Can be changed by passing in this shell file. 131 | If you want to the same result from paper\[1\], please use this [pre training model.](http://data.airc.aist.go.jp/MUSIC4P3dataset/cnn_nm_ep1930.net) 132 | 133 | 134 | ### Chainer ver 135 | #### Requierment 136 | * Python 2.7.* 137 | * Chainer 1.24.* 138 | For chainer Installition, see from https://chainer.org/ 139 | 140 | #### Training 141 | Type the following in the terminal. 142 | 143 | ``` 144 | $ sh train.sh 145 | ``` 146 | 147 | Models are save to ./result/ (the output directory is specified in train.sh) . 148 | 149 | #### Testing 150 | 151 | Type the following in the terminal. 152 | 153 | ``` 154 | $ sh test.sh > result.log 155 | ``` 156 | 157 | Result file are created to ./result.log . 158 | If you want to the same result from paper\[1\], please use this [pre training model.](http://data.airc.aist.go.jp/MUSIC4P3dataset/newmegasolarCNN_NS_72720_3725_iteration.model) 159 | 160 | ## Acknowledgement 161 | This dataset and source code are based on results obtained from a project commissioned by the New Energy and Industrial Technology Development Organization (NEDO). 162 | -------------------------------------------------------------------------------- /torch/train-cnn.lua: -------------------------------------------------------------------------------- 1 | 2 | require 'cunn' 3 | require 'optim' 4 | require 'image' 5 | require 'hdf5' 6 | require 'cutorch' 7 | require 'lfs' 8 | 9 | package.path = package.path..';torch-toolbox-master/Sanitize/?.lua' 10 | 11 | sanitize = require 'sanitize' 12 | 13 | fname = 'train-cnn' 14 | 15 | 16 | -- 17 | cmd = torch.CmdLine() 18 | cmd:text() 19 | cmd:text('Megasolar Training') 20 | cmd:text() 21 | cmd:text('Options:') 22 | cmd:option('-save', fname, 'subdirectory to save/log experiments in') 23 | cmd:option('-network', '', 'reload pretrained network') 24 | cmd:option('-model', 'convnet', 'type of model to train: convnet | mlp | linear') 25 | cmd:option('-full', false, 'use full dataset (50,000 samples)') 26 | cmd:option('-visualize', false, 'visualize input data and weights during training') 27 | cmd:option('-seed', 1, 'fixed input seed for repeatable experiments') 28 | cmd:option('-optimization', 'SGD', 'optimization method: SGD | ASGD | CG | LBFGS') 29 | cmd:option('-learningRate', 1e-3, 'learning rate at t=0') 30 | cmd:option('-batchSize', 32, 'mini-batch size (1 = pure stochastic)') 31 | cmd:option('-weightDecay', 0, 'weight decay (SGD only)') 32 | cmd:option('-momentum', 0.9, 'momentum (SGD only)') 33 | cmd:option('-t0', 1, 'start averaging at t0 (ASGD only), in nb of epochs') 34 | cmd:option('-maxIter', 5, 'maximum nb of iterations for CG and LBFGS') 35 | cmd:option('-threads', 2, 'nb of threads to use') 36 | cmd:option('-testInterval', 10, 'test interval') 37 | cmd:option('-saveInterval', 500, 'save interval') 38 | cmd:option('-inputChannel', 7, 'nb of input channels') 39 | cmd:option('-maxEpoch', 1000, 'maximum nb of epochs') 40 | cmd:option('-testBatchSize', 10000, 'test batch size') 41 | cmd:option('-gpu', 1, 'id of gpu to use') 42 | cmd:option('-dropout', false, '') 43 | cmd:option('-batchNorm', false, '') 44 | cmd:option('-negativeBatchSize', 128, 'negative batch size for negative mining') 45 | cmd:option('-batchSizeNM', 32, 'mini-batch size for negative mining') 46 | cmd:option('-epochStartNM', 5000, 'nb of epochs to start negative mining') 47 | cmd:option('-trainDataPath', '', 'path of hdf5 dataset for training') 48 | cmd:option('-testDataPath', '', 'path of hdf5 dataset for test') 49 | cmd:option('-negativeRatio', 1, 'batch ratio, 1 : x = positive : negative') 50 | cmd:option('-trainnorm', 'mean_stdv.csv', 'output training data mean and stdv path') 51 | cmd:text() 52 | opt = cmd:parse(arg) 53 | -- 54 | 55 | print (opt.optimization) 56 | 57 | cutorch.manualSeed(opt.seed) 58 | cutorch.setDevice(opt.gpu) 59 | 60 | classes = {'positive', 'negative'} 61 | accLogger = optim.Logger(paths.concat(opt.save, 'accuracy.log')) 62 | errLogger = optim.Logger(paths.concat(opt.save, 'error.log' )) 63 | 64 | if opt.network == '' then 65 | -- define model to train 66 | model = nn.Sequential() 67 | if opt.model == 'convnet' then 68 | ------------------------------------------------------------ 69 | -- convolutional network 70 | ------------------------------------------------------------ 71 | -- stage 1 72 | model:add(nn.SpatialConvolution(opt.inputChannel, 32, 3, 3)) 73 | model:add(nn.ReLU()) 74 | if opt.batchNorm ~= false then 75 | model:add(nn.SpatialBatchNormalization(32)) 76 | end 77 | 78 | -- stage 2 79 | model:add(nn.SpatialConvolution(32, 32, 3, 3)) 80 | model:add(nn.ReLU()) 81 | if opt.batchNorm ~= false then 82 | model:add(nn.SpatialBatchNormalization(32)) 83 | end 84 | 85 | -- stage 3 86 | model:add(nn.SpatialConvolution(32, 32, 3, 3)) 87 | model:add(nn.ReLU()) 88 | if opt.batchNorm ~= false then 89 | model:add(nn.SpatialBatchNormalization(32)) 90 | end 91 | 92 | 93 | -- stage 4 94 | model:add(nn.Reshape(32*10*10)) 95 | if opt.dropout ~= false then 96 | model:add(nn.Dropout(0.5)) 97 | end 98 | model:add(nn.Linear(32*10*10,#classes)) 99 | 100 | ------------------------------------------------------------ 101 | else 102 | print('Unknown model type') 103 | cmd:text() 104 | error() 105 | end 106 | else 107 | print(' reloading previously trained network') 108 | model = nn.Sequential() 109 | model = torch.load(opt.network) 110 | end 111 | 112 | -- verbose 113 | print('using model:') 114 | print(model) 115 | 116 | ---------------------------------------------------------------------- 117 | -- loss function: negative log-likelihood 118 | -- 119 | model:add(nn.LogSoftMax()) 120 | model:cuda() 121 | 122 | -- retrieve parameters and gradients 123 | parameters,gradParameters = model:getParameters() 124 | 125 | criterion = nn.ClassNLLCriterion():cuda() 126 | 127 | ---------------------------------------------------------------------- 128 | -- Import Data 129 | -- 130 | 131 | function getFileList(dirPath, ext) 132 | list = {} 133 | for file in lfs.dir(dirPath) do 134 | if string.find(file, ext..'$') then 135 | table.insert(list,file) 136 | end 137 | end 138 | return list 139 | end 140 | 141 | numTrainPositive = 0 -- the number of training positive data 142 | numTrainNegative = 0 -- the number of training negative data 143 | 144 | if lfs.attributes(opt.trainDataPath)['mode'] == 'file' then 145 | -- import training data 146 | print("Import training data...") 147 | myFile = hdf5.open(opt.trainDataPath,'r') 148 | trainData = myFile:read(''):all() 149 | myFile:close() 150 | labelNum = trainData.label:size()[1] 151 | 152 | for i = 1,labelNum do 153 | if trainData.label[i] == 0 then 154 | trainData.label[i] = 1 155 | numTrainPositive = numTrainPositive + 1 156 | else 157 | trainData.label[i] = 2 158 | numTrainNegative = numTrainNegative + 1 159 | end 160 | end 161 | 162 | numAllTrainData = labelNum 163 | 164 | elseif lfs.attributes(opt.trainDataPath)['mode'] == 'directory' then 165 | hdfList = getFileList(opt.trainDataPath,'hdf5') 166 | dataList = {} 167 | for i=1, #hdfList do 168 | local myFile = hdf5.open(opt.trainDataPath..'/'..hdfList[i]) 169 | print('Importing:'..opt.trainDataPath..'/'..hdfList[i]) 170 | tempTrainData = myFile:read(''):all() 171 | myFile:close() 172 | labelNum = tempTrainData.label:size()[1] 173 | 174 | for i = 1,labelNum do 175 | if tempTrainData.label[i] == 0 then 176 | tempTrainData.label[i] = 1 177 | numTrainPositive = numTrainPositive + 1 178 | else 179 | tempTrainData.label[i] = 2 180 | numTrainNegative = numTrainNegative + 1 181 | end 182 | end 183 | table.insert(dataList, tempTrainData) 184 | end 185 | numAllTrainData = 0 186 | for i=1, #dataList do 187 | numAllTrainData = numAllTrainData + dataList[i]['data']:size()[1] 188 | end 189 | print(numAllTrainData) 190 | trainData = { 191 | data=torch.Tensor(numAllTrainData, opt.inputChannel, dataList[1]['data']:size()[3], dataList[1]['data']:size()[4]), 192 | label=torch.Tensor(numAllTrainData), 193 | size = function() return numAllTrainData end 194 | } 195 | k = 1 196 | for i=1, #dataList do 197 | for j=1, dataList[i]['data']:size()[1] do 198 | trainData.data[k] = dataList[i]['data'][j] 199 | trainData.label[k] = dataList[i]['label'][j] 200 | k = k + 1 201 | end 202 | end 203 | 204 | else 205 | error("invalid dataset") 206 | end 207 | 208 | dataMean = {} 209 | dataStd = {} 210 | 211 | file = io.open(opt.trainnorm,"w") 212 | for t=1,opt.inputChannel do 213 | local trainMean = trainData.data[{{},t,{},{}}]:mean() 214 | local trainStd = trainData.data[{{},t,{},{}}]:std() 215 | trainData.data[{ {},t,{},{} }]:add(-trainMean) 216 | trainData.data[{ {},t,{},{} }]:div(trainStd) 217 | print(t.."ch mean:"..trainMean.." stdv:"..trainStd) 218 | file:write(trainMean..","..trainStd.."\n") 219 | table.insert(dataMean, trainMean) 220 | table.insert(dataStd, trainStd) 221 | end 222 | file:close() 223 | 224 | positiveList = {} 225 | negativeList = {} 226 | for i = 1, trainData:size() do 227 | if trainData.label[i] == 1 then 228 | table.insert(positiveList,i) 229 | else 230 | table.insert(negativeList,i) 231 | end 232 | end 233 | 234 | trainData = { 235 | data = trainData.data, 236 | labels = trainData.label, 237 | size = function() return numAllTrainData end, 238 | addressList = {positiveList,negativeList} 239 | } 240 | 241 | 242 | 243 | -- import test data 244 | if lfs.attributes(opt.testDataPath)['mode'] == 'file' then 245 | 246 | print("Import test data...") 247 | print('Importing:'..opt.testDataPath) 248 | myFile = hdf5.open(opt.testDataPath,'r') 249 | testData = myFile:read(''):all() 250 | myFile:close() 251 | labelNum = testData.label:size()[1] 252 | 253 | for i = 1,labelNum do 254 | if testData.label[i] == 0 then 255 | testData.label[i] = 1 256 | else 257 | testData.label[i] = 2 258 | end 259 | end 260 | 261 | numAllTestData = labelNum 262 | 263 | elseif lfs.attributes(opt.testDataPath)['mode'] == 'directory' then 264 | hdfList = getFileList(opt.testDataPath,'hdf5') 265 | dataList = {} 266 | for i=1, #hdfList do 267 | local myFile = hdf5.open(opt.testDataPath..'/'..hdfList[i]) 268 | print('Importing:'..opt.testDataPath..'/'..hdfList[i]) 269 | tempTestData = myFile:read(''):all() 270 | myFile:close() 271 | labelNum = tempTestData.label:size()[1] 272 | 273 | for i = 1,labelNum do 274 | if tempTestData.label[i] == 0 then 275 | tempTestData.label[i] = 1 276 | else 277 | tempTestData.label[i] = 2 278 | end 279 | end 280 | table.insert(dataList, tempTestData) 281 | end 282 | numAllTestData = 0 283 | for i=1, #dataList do 284 | numAllTestData = numAllTestData + dataList[i]['data']:size()[1] 285 | end 286 | print(numAllTestData) 287 | testData = { 288 | data=torch.Tensor(numAllTestData, opt.inputChannel, dataList[1]['data']:size()[3], dataList[1]['data']:size()[4]), 289 | label=torch.Tensor(numAllTestData), 290 | size = function() return numAllTestData end 291 | } 292 | k = 1 293 | for i=1, #dataList do 294 | for j=1, dataList[i]['data']:size()[1] do 295 | testData.data[k] = dataList[i]['data'][j] 296 | testData.label[k] = dataList[i]['label'][j] 297 | k = k + 1 298 | end 299 | end 300 | 301 | else 302 | error("invalid dataset") 303 | end 304 | 305 | for t=1,opt.inputChannel do 306 | testData.data[{ {},t,{},{} }]:add(-dataMean[t]) 307 | testData.data[{ {},t,{},{} }]:div(dataStd[t]) 308 | print(t.."ch mean:"..dataMean[t].." stdv:"..dataStd[t]) 309 | end 310 | 311 | testData = { 312 | data = testData.data, 313 | labels = testData.label, 314 | size = function() return numAllTestData end 315 | } 316 | 317 | ---------------------------------------------------------------------- 318 | -- define training and testing functions 319 | -- 320 | 321 | -- this matrix records the current confusion across classes 322 | confusion = optim.ConfusionMatrix(classes) 323 | 324 | -- log results to files 325 | print (opt.save) 326 | accLogger.showPlot = false 327 | errLogger.showPlot = false 328 | 329 | function dataAugmentation(batchData) 330 | outputBatchData = torch.Tensor(batchData:size()[1],batchData:size()[2],batchData:size()[3],batchData:size()[4]):float() 331 | inputBatchSize = batchData:size()[1] 332 | hRand = torch.rand(inputBatchSize) 333 | vRand = torch.rand(inputBatchSize) 334 | for i=1, inputBatchSize do 335 | if hRand[i] > 0.5 then 336 | outputBatchData[i] = image.hflip(batchData[i]:float()) 337 | else 338 | outputBatchData[i] = batchData[i]:float() 339 | end 340 | if vRand[i] > 0.5 then 341 | outputBatchData[i] = image.vflip(outputBatchData[i]) 342 | end 343 | end 344 | return outputBatchData 345 | end 346 | 347 | -- training function 348 | function train(dataset) 349 | -- epoch tracker 350 | epoch = epoch or 1 351 | 352 | model:training() 353 | 354 | -- local vars 355 | local time = sys.clock() 356 | local trainError = 0 357 | 358 | -- do one epoch 359 | print(' on training set:') 360 | print(" online epoch # " .. epoch .. ' [batchSize = ' .. opt.batchSize .. ']') 361 | for t = 1,dataset:size(),opt.batchSize do 362 | 363 | 364 | local inputs = dataset.data[{{t,math.min(t+opt.batchSize-1,dataset:size())},{},{},{}}] 365 | local targets = dataset.labels[{{t,math.min(t+opt.batchSize-1,dataset:size())}}] 366 | 367 | -- data augmentation 368 | inputs = dataAugmentation(inputs):cuda() 369 | targets = targets:cuda() 370 | 371 | -- create closure to evaluate f(X) and df/dX 372 | local feval = function(x) 373 | -- get new parameters 374 | if x ~= parameters then 375 | parameters:copy(x) 376 | end 377 | 378 | -- reset gradients 379 | gradParameters:zero() 380 | 381 | -- f is the average of all criterions 382 | local f = 0 383 | 384 | 385 | local output = model:forward(inputs) 386 | local err = criterion:forward(output, targets) 387 | f = f + err 388 | -- estimate df/dW 389 | local df_do = criterion:backward(output, targets) 390 | model:backward(inputs, df_do) 391 | for i = 1,math.min(opt.batchSize,dataset:size()-t+1) do 392 | -- update confusion 393 | confusion:add(output[i], targets[i]) 394 | end 395 | 396 | 397 | trainError = trainError + f 398 | 399 | -- return f and df/dX 400 | return f,gradParameters 401 | end 402 | 403 | -- optimize on current mini-batch 404 | if opt.optimization == 'CG' then 405 | config = config or {maxIter = opt.maxIter} 406 | optim.cg(feval, parameters, config) 407 | 408 | elseif opt.optimization == 'LBFGS' then 409 | config = config or {learningRate = opt.learningRate, 410 | maxIter = opt.maxIter, 411 | nCorrection = 10} 412 | optim.lbfgs(feval, parameters, config) 413 | 414 | elseif opt.optimization == 'SGD' then 415 | config = config or {learningRate = opt.learningRate, 416 | weightDecay = opt.weightDecay, 417 | momentum = opt.momentum, 418 | learningRateDecay = 5e-7} 419 | optim.sgd(feval, parameters, config) 420 | 421 | elseif opt.optimization == 'ASGD' then 422 | config = config or {eta0 = opt.learningRate, 423 | t0 = nbTrainingPatches * opt.t0} 424 | _,_,average = optim.asgd(feval, parameters, config) 425 | 426 | elseif opt.optimization == 'ADAM' then 427 | config = config or {learningRate = opt.learningRate, 428 | learningRateDecay = 5e-7, 429 | beta1 = 0.9, 430 | beta2 = 0.999, 431 | epsilon = 1e-8, 432 | weightDecay = opt.weightDecay} 433 | optim.adam(feval, parameters, config) 434 | 435 | else 436 | error('unknown optimization method') 437 | end 438 | end 439 | 440 | -- train error 441 | trainError = trainError / math.floor(dataset:size()/opt.batchSize) 442 | 443 | -- time taken 444 | time = sys.clock() - time 445 | allTime = time 446 | time = time / dataset:size() 447 | print(" time to learn 1 sample = " .. (time*1000) .. 'ms') 448 | print(" time to learn all samples = " .. (allTime*1000) .. 'ms') 449 | 450 | -- print confusion matrix 451 | print(confusion) 452 | local trainAccuracy = confusion.totalValid * 100 453 | confusion:zero() 454 | 455 | -- save/log current net 456 | if epoch%opt.saveInterval==0 then 457 | local filename = paths.concat(opt.save, 'cnn_ep'..epoch..'.net') 458 | os.execute('mkdir -p ' .. paths.dirname(filename)) 459 | if paths.filep(filename) then 460 | os.execute('mv ' .. filename .. ' ' .. filename .. '.old') 461 | end 462 | print(' saving network to '..filename) 463 | torch.save(filename, sanitize(model)) 464 | end 465 | 466 | -- next epoch 467 | epoch = epoch + 1 468 | 469 | return trainAccuracy, trainError 470 | end 471 | 472 | -- training function using negative mining 473 | function trainMod(dataset) 474 | -- epoch tracker 475 | epoch = epoch or 1 476 | 477 | model:training() 478 | 479 | 480 | 481 | -- local vars 482 | local time = sys.clock() 483 | local trainError = 0 484 | 485 | batch_rate = 1 + opt.negativeRatio 486 | 487 | -- do one epoch 488 | print(' on training set:') 489 | print(" online epoch # " .. epoch .. ' [batchSize = ' .. opt.batchSize .. ']') 490 | 491 | 492 | local dataSize = dataset.data:size() 493 | pIdx = torch.randperm(#dataset.addressList[1]) 494 | nIdx = torch.randperm(#dataset.addressList[2]) 495 | addressListmin=math.min(#dataset.addressList[1],#dataset.addressList[2]) 496 | BatchData = { 497 | data = torch.Tensor(addressListmin*batch_rate,dataSize[2],dataSize[3],dataSize[4]), 498 | labels = torch.Tensor(addressListmin*batch_rate), 499 | size = function() return addressListmin*batch_rate end, 500 | } 501 | 502 | for i=1, addressListmin do 503 | BatchData.data[batch_rate*i-batch_rate+1] = dataset.data[dataset.addressList[1][pIdx[i]]] 504 | BatchData.labels[batch_rate*i-batch_rate+1] = dataset.labels[dataset.addressList[1][pIdx[i]]] 505 | for j=1, batch_rate-1 do 506 | BatchData.data[batch_rate*i-batch_rate+j+1] = dataset.data[dataset.addressList[2][nIdx[(batch_rate-1)*i+1-j]]] 507 | BatchData.labels[batch_rate*i-batch_rate+j+1] = dataset.labels[dataset.addressList[2][nIdx[(batch_rate-1)*i+1-j]]] 508 | end 509 | end 510 | 511 | 512 | for t = 1,BatchData:size(),opt.batchSize do 513 | 514 | 515 | local inputs = BatchData.data[{{t,math.min(t+opt.batchSize-1,BatchData:size())},{},{},{}}] 516 | local targets = BatchData.labels[{{t,math.min(t+opt.batchSize-1,BatchData:size())}}] 517 | 518 | -- data augmentation 519 | inputs = dataAugmentation(inputs):cuda() 520 | targets = targets:cuda() 521 | 522 | -- create closure to evaluate f(X) and df/dX 523 | local feval = function(x) 524 | -- get new parameters 525 | if x ~= parameters then 526 | parameters:copy(x) 527 | end 528 | 529 | -- reset gradients 530 | gradParameters:zero() 531 | 532 | -- f is the average of all criterions 533 | local f = 0 534 | 535 | 536 | local output = model:forward(inputs) 537 | local err = criterion:forward(output, targets) 538 | f = f + err 539 | -- estimate df/dW 540 | local df_do = criterion:backward(output, targets) 541 | model:backward(inputs, df_do) 542 | for i = 1,math.min(opt.batchSize,BatchData:size()-t+1) do 543 | -- update confusion 544 | confusion:add(output[i], targets[i]) 545 | end 546 | 547 | -- normalize gradients and f(X) 548 | 549 | trainError = trainError + f 550 | 551 | -- return f and df/dX 552 | return f,gradParameters 553 | end 554 | 555 | -- optimize on current mini-batch 556 | if opt.optimization == 'CG' then 557 | config = config or {maxIter = opt.maxIter} 558 | optim.cg(feval, parameters, config) 559 | 560 | elseif opt.optimization == 'LBFGS' then 561 | config = config or {learningRate = opt.learningRate, 562 | maxIter = opt.maxIter, 563 | nCorrection = 10} 564 | optim.lbfgs(feval, parameters, config) 565 | 566 | elseif opt.optimization == 'SGD' then 567 | config = config or {learningRate = opt.learningRate, 568 | weightDecay = opt.weightDecay, 569 | momentum = opt.momentum, 570 | learningRateDecay = 5e-7} 571 | optim.sgd(feval, parameters, config) 572 | 573 | elseif opt.optimization == 'ASGD' then 574 | config = config or {eta0 = opt.learningRate, 575 | t0 = nbTrainingPatches * opt.t0} 576 | _,_,average = optim.asgd(feval, parameters, config) 577 | 578 | elseif opt.optimization == 'ADAM' then 579 | config = config or {learningRate = opt.learningRate, 580 | learningRateDecay = 5e-7, 581 | beta1 = 0.9, 582 | beta2 = 0.999, 583 | epsilon = 1e-8, 584 | weightDecay = opt.weightDecay} 585 | optim.adam(feval, parameters, config) 586 | else 587 | error('unknown optimization method') 588 | end 589 | end 590 | 591 | -- train error 592 | trainError = trainError / math.floor(BatchData:size()/opt.batchSize) 593 | 594 | -- time taken 595 | time = sys.clock() - time 596 | allTime = time 597 | time = time / BatchData:size() 598 | print(" time to learn 1 sample = " .. (time*1000) .. 'ms') 599 | print(" time to learn all samples = " .. (allTime*1000) .. 'ms') 600 | 601 | -- print confusion matrix 602 | print(confusion) 603 | local trainAccuracy = confusion.totalValid * 100 604 | confusion:zero() 605 | 606 | -- save/log current net 607 | if epoch%opt.saveInterval==0 then 608 | local filename = paths.concat(opt.save, 'cnn_nm_ep'..epoch..'.net') 609 | os.execute('mkdir -p ' .. paths.dirname(filename)) 610 | if paths.filep(filename) then 611 | os.execute('mv ' .. filename .. ' ' .. filename .. '.old') 612 | end 613 | print(' saving network to '..filename) 614 | torch.save(filename, sanitize(model)) 615 | end 616 | 617 | -- next epoch 618 | epoch = epoch + 1 619 | 620 | return trainAccuracy, trainError 621 | end 622 | 623 | -- training function using negative mining 624 | function trainNM(dataset) 625 | -- epoch tracker 626 | epoch = epoch or 1 627 | 628 | model:training() 629 | 630 | -- batchNM = 128 631 | 632 | -- local vars 633 | local time = sys.clock() 634 | local trainError = 0 635 | 636 | -- do one epoch 637 | print(' on training set:') 638 | print(" online epoch # " .. epoch .. ' [batchSize = ' .. opt.batchSizeNM .. ']') 639 | 640 | 641 | -- get 128 negative data 642 | i = 1 643 | local dataSize = dataset.data:size() 644 | local negativeList = {} 645 | local nInputs = torch.Tensor(opt.negativeBatchSize,dataSize[2],dataSize[3],dataSize[4]) 646 | 647 | 648 | local nTargets = torch.Tensor(opt.negativeBatchSize) 649 | 650 | while i <= opt.negativeBatchSize do 651 | randN = (torch.rand(1)*(dataSize[1]-1)):floor()[1]+1 652 | j = 1 653 | count = 0 654 | while j <= #negativeList do 655 | if randN == negativeList[j] then 656 | count = count + 1 657 | end 658 | j = j + 1 659 | end 660 | 661 | if dataset.labels[randN] == 2 and count == 0 then 662 | table.insert(negativeList,randN) 663 | 664 | nInputs[i] = dataset.data[randN]:double() 665 | 666 | nTargets[i] = dataset.labels[randN] 667 | 668 | i = i + 1 669 | end 670 | end 671 | 672 | 673 | 674 | -- get all positive data 675 | positiveList = {} 676 | for i = 1, dataset:size() do 677 | if dataset.labels[i] == 1 then 678 | table.insert(positiveList,i) 679 | end 680 | end 681 | 682 | pIdx = torch.randperm(#positiveList) 683 | 684 | 685 | -- get loss of the negative data 686 | local output = model:forward(nInputs:cuda()) 687 | --local err = criterion:forward(output, nTargets:cuda()) 688 | local errList = torch.Tensor(opt.negativeBatchSize) 689 | 690 | for i = 1, opt.negativeBatchSize do 691 | errList[i] = criterion:forward(output[i], nTargets[i]) 692 | end 693 | 694 | 695 | -- sort the loss in descending order 696 | 697 | y, idx = torch.sort(errList:double(),1,true) 698 | 699 | -- select 16 larger loss for training and create mini-batch 700 | local inputs = torch.Tensor(opt.batchSizeNM,dataSize[2],dataSize[3],dataSize[4]):cuda() 701 | local targets = torch.Tensor(opt.batchSizeNM):cuda() 702 | for i = 1, (opt.batchSizeNM/2) do 703 | 704 | inputs[i] = nInputs[idx[i]] 705 | targets[i] = nTargets[idx[i]] 706 | end 707 | for i=1, (opt.batchSizeNM/2) do 708 | inputs[i+(opt.batchSizeNM/2)] = dataset.data[positiveList[pIdx[i]]] 709 | targets[i+(opt.batchSizeNM/2)] = dataset.labels[positiveList[pIdx[i]]] 710 | end 711 | 712 | 713 | -- data augmentation 714 | inputs = dataAugmentation(inputs):cuda() 715 | targets:cuda() 716 | 717 | -- training 718 | -- create closure to evaluate f(X) and df/dX 719 | local feval = function(x) 720 | -- get new parameters 721 | if x ~= parameters then 722 | parameters:copy(x) 723 | end 724 | 725 | -- reset gradients 726 | gradParameters:zero() 727 | 728 | -- f is the average of all criterions 729 | local f = 0 730 | 731 | local output = model:forward(inputs) 732 | local err = criterion:forward(output, targets) 733 | f = f + err 734 | -- estimate df/dW 735 | local df_do = criterion:backward(output, targets) 736 | model:backward(inputs, df_do) 737 | for i = 1,opt.batchSizeNM do 738 | -- update confusion 739 | confusion:add(output[i], targets[i]) 740 | end 741 | 742 | trainError = trainError + f 743 | 744 | -- return f and df/dX 745 | return f,gradParameters 746 | end 747 | 748 | -- optimize on current mini-batch 749 | if opt.optimization == 'CG' then 750 | config = config or {maxIter = opt.maxIter} 751 | optim.cg(feval, parameters, config) 752 | 753 | elseif opt.optimization == 'LBFGS' then 754 | config = config or {learningRate = opt.learningRate, 755 | maxIter = opt.maxIter, 756 | nCorrection = 10} 757 | optim.lbfgs(feval, parameters, config) 758 | 759 | elseif opt.optimization == 'SGD' then 760 | config = config or {learningRate = opt.learningRate, 761 | weightDecay = opt.weightDecay, 762 | momentum = opt.momentum, 763 | learningRateDecay = 5e-7} 764 | optim.sgd(feval, parameters, config) 765 | 766 | elseif opt.optimization == 'ASGD' then 767 | config = config or {eta0 = opt.learningRate, 768 | t0 = nbTrainingPatches * opt.t0} 769 | _,_,average = optim.asgd(feval, parameters, config) 770 | 771 | elseif opt.optimization == 'ADAM' then 772 | config = config or {learningRate = opt.learningRate, 773 | learningRateDecay = 5e-7, 774 | beta1 = 0.9, 775 | beta2 = 0.999, 776 | epsilon = 1e-8, 777 | weightDecay = opt.weightDecay} 778 | optim.adam(feval, parameters, config) 779 | 780 | else 781 | error('unknown optimization method') 782 | end 783 | 784 | -- train error 785 | -- time taken 786 | time = sys.clock() - time 787 | allTime = time 788 | print(" time to learn all samples = " .. (allTime*1000) .. 'ms') 789 | 790 | -- print confusion matrix 791 | print(confusion) 792 | local trainAccuracy = confusion.totalValid * 100 793 | confusion:zero() 794 | 795 | -- save/log current net 796 | if epoch%opt.saveInterval==0 then 797 | local filename = paths.concat(opt.save, 'cnn_nm_ep'..epoch..'.net') 798 | os.execute('mkdir -p ' .. paths.dirname(filename)) 799 | if paths.filep(filename) then 800 | os.execute('mv ' .. filename .. ' ' .. filename .. '.old') 801 | end 802 | print(' saving network to '..filename) 803 | torch.save(filename, sanitize(model)) 804 | end 805 | 806 | -- next epoch 807 | epoch = epoch + 1 808 | 809 | return trainAccuracy, trainError 810 | end 811 | 812 | 813 | -- test function 814 | function testRapid(dataset) 815 | 816 | model:evaluate() 817 | 818 | -- local vars 819 | local testError = 0 820 | local time = sys.clock() 821 | 822 | -- averaged param use? 823 | if average then 824 | cachedparams = parameters:clone() 825 | parameters:copy(average) 826 | end 827 | 828 | --local testBatch = 30000 829 | local numErr = 0 830 | --print('1') 831 | for t = 1,dataset:size(),opt.testBatchSize do 832 | -- disp progress 833 | -- xlua.progress(t, dataset:size()) 834 | local batchStart = t 835 | local batchEnd = math.min(t+opt.testBatchSize-1,dataset:size()) 836 | local inputs = dataset.data[{{batchStart,batchEnd},{},{},{}}]:cuda() 837 | local targets = dataset.labels[{{batchStart,batchEnd}}]:cuda() 838 | 839 | local preds = model:forward(inputs) 840 | for i = 1, math.min(opt.testBatchSize,dataset:size()-t+1) do 841 | confusion:add(preds[i], targets[i]) 842 | end 843 | 844 | err = criterion:forward(preds, targets) 845 | testError = testError + err 846 | numErr = numErr + 1 847 | end 848 | 849 | -- timing 850 | time = sys.clock() - time 851 | allTime = time 852 | time = time / dataset:size() 853 | print(" time to test 1 sample = " .. (time*1000) .. 'ms') 854 | print(" time to test all samples = " .. (allTime*1000) .. 'ms') 855 | 856 | -- testing error estimation 857 | -- testError = testError / dataset:size() 858 | testError = testError / numErr 859 | 860 | -- print confusion matrix 861 | print(confusion) 862 | local testAccuracy = confusion.totalValid * 100 863 | confusion:zero() 864 | 865 | -- averaged param use? 866 | if average then 867 | -- restore parameters 868 | parameters:copy(cachedparams) 869 | end 870 | 871 | return testAccuracy, testError 872 | end 873 | 874 | n=1 875 | 876 | while n <= opt.maxEpoch do 877 | -- train/test 878 | if n <= opt.epochStartNM then 879 | trainAcc, trainErr = trainMod(trainData) 880 | if n%opt.testInterval == 0 or n == 1 then 881 | testAcc, testErr = testRapid(testData) 882 | end 883 | else 884 | -- negative mining 885 | trainAcc, trainErr = trainNM(trainData) 886 | if n%(opt.testInterval*10) == 0 then 887 | testAcc, testErr = testRapid(testData) 888 | end 889 | end 890 | 891 | 892 | -- update logger 893 | accLogger:add{['% train accuracy'] = trainAcc, ['% test accuracy'] = testAcc} 894 | errLogger:add{['% train error'] = trainErr, ['% test error'] = testErr} 895 | 896 | -- plot logger 897 | accLogger:style{['% train accuracy'] = '-', ['% test accuracy'] = '-'} 898 | errLogger:style{['% train error'] = '-', ['% test error'] = '-'} 899 | accLogger:plot() 900 | errLogger:plot() 901 | 902 | collectgarbage() 903 | 904 | collectgarbage() 905 | 906 | n = n + 1 907 | end 908 | --------------------------------------------------------------------------------