def plot_filterbank_gammatone(ax, fs=44100): np = numpy from pyfilterbank import gammatone gfb = gammatone.GammatoneFilterbank(samplerate=44100, startband=-6, endband=26, density=1.5) def plotfun(x, y): xx = x * fs #print('s', numpy.min(x), numpy.max(x), numpy.max(xx)) ax.plot(xx, 20 * np.log10(np.abs(y) + 1e-9)) gfb.freqz(nfft=2 * 4096, plotfun=plotfun) #fig.set_grid(True) ax.set_title('Gammatone') #ax.set_xlabel('Frequency (Hz)') #ax.set_axis('Tight') ax.set_ylim([-80, 1]) ax.set_xlim([10, 20e3]) # plt.show() return gfb
def run_SFA(cur_pair, vocal_foldername, vocal_pair, clutterfoldername, clutterfiletemplate, run_nulls): #New range 2020-08-04 snr_values = np.array( [1e-7, 1e-5, 1e-3, .1, 1.0, 10.0, 100.0, 1000.0] ) #snr range, run noiseless as well just separately since that is a toggle in the code #Save SFA classification accuracy and the baseline model score SFA_save_score = np.zeros((5, snr_values.size)) Baseline_save_score = np.zeros((5, snr_values.size)) #Reduced rounds to 5 for sake of error bars. #Note will need to run some null sets too, add this to batch code and add argument to run_SFA for a_round in range( 0, 5 ): #rounds are for averaging over for each set of snr (i.e. each snr is done 10 times) print(a_round) for iteration in range( 0, snr_values.size): #iterations for doing each snr print(snr_values[iteration]) ## Files for vocalizations and noise load_noise = True #toggle whether noises is generated or pulled in from a pre-generated file noiselen = 100000 #if loading in a pre-generated file, only take this many samples noise = True #toggle for whether testing with noise or not #this works using pathlib see outer file for explanation voc1 = vocal_foldername / vocal_pair[0][0] voc2 = vocal_foldername / vocal_pair[1][0] #convert to windows path i.e using \ instead of / voc1 = PureWindowsPath(voc1) voc2 = PureWindowsPath(voc2) #set up list of vocal files to load vocal_files = [voc1, voc2] #Get the clutter file for this particular pair. Add 1 to the cur_pair due to difference in indexing between matlab and python clutter_file = str(cur_pair + 1) + clutterfiletemplate clutter_file = Path(clutter_file) noise_file = clutterfoldername / clutter_file #Same as above now switch to windows path noise_file = PureWindowsPath(noise_file) num_vocals = len( vocal_files ) #for use later, get number of unique stimulus files loaded ## Parameters for vocalization and noise pre processing signal_to_noise_ratio = snr_values[ iteration] #scales by average power across noise and vocalizations gfb = g.GammatoneFilterbank( order=1, density=1.0, startband=-21, endband=21, normfreq=2200 ) #sets up parameters for our gammatone filter model of the cochlea. #Need to look at documentation to figure out exactly how these parameters work , but normfreq at least seems to be central frequency from #which the rest of the fitler a distributed (accoruding to startband and endband) down_sample = True #down sample stimulus to help with RAM issues and general dimensionality issues. I believe mostly reduces resolution of frequency #2020-07-20 probably will turn_sample pre down to 1 since that ends up looking more correct/because I think kilosort can handle it #Also can try to run it with no downsampling since kilosort has more RAM. It will make things run slower but will be more accurate #new down_samples as of 08-04-2020 down_sample_pre = 2 #Factor by which to reduce Fs by (e.g. 10 reduces Fs by one order of magnitude) down_sample_post = 2 #Factor by which to reduce Fs after applying filters ## Parameters for training data #new number of presentations, just 1 num_samples = num_vocals * 1 #note current results on screen were with 15 examples, trying one last go at three vocalization then going to cut loses for tonight and stop #choose how many times you see each stimulus gaps = True #toggle whether there can be gaps between presentation of each stimulus apply_noise = True #toggle for applying noise ## Parameters for testing data #leave test noise off for now, add functionality to use fully novel noise later. test_noise = False #Toggle for adding unique noise in test case that is different from training case #2020-08-04 new classifier as well as using only 5 features. classifier_baseline = LinearSVC( max_iter=10000000, tol=0.001 ) #Perceptron(max_iter = 10000, tol = 0.001) #from scikit (presumably) #set up Perceptron classifier classifier_SFA = LinearSVC( max_iter=10000000, tol=0.001) #Perceptron(max_iter = 10000, tol = 0.001) classifier_features = 5 #how many features from SFA SFA classifer gets to use baseline_features = 'all' #how many features the Perceptron by itself gets to use ## Load in files vocalizations = get_data( vocal_files ) #get list object where each entry is a numpy array of each vocal file print('Vocalizations Loaded...') ##Load in and adjust noise power accordingly to sigal to noise ratio if (load_noise): noise, _ = sf.read(noise_file) print('Noises loaded...') print('Ready for preprocessing.') if noise is not None: noise = scale_noise( vocalizations, noise, signal_to_noise_ratio) #scales based on average power noise = noise[:noiselen] print('Noise Scaled...') print('Ready For Gammatone Transform') ## Apply Gammatone Transform to signal and noise vocals_transformed = gamma_transform_list( vocalizations, gfb ) #does what is says on the tin: applies gamma transform to list of numpy arrays print('Vocalizations Transformed...') if noise is not None: noise_transformed = gamma_transform(noise, gfb) print('Noise Transformed...') ## Down sample for computation tractablility #reeval gammatone transform accordingly if down_sample: #update 2020-01-21 downsample noise at this step too for our more structured noise for i, vocal in enumerate(vocals_transformed): vocals_transformed[ i] = vocal[:, :: down_sample_pre] #down samples by factor set in above code (e.g. 10 means reduce fs by one order of magnitude) if noise is not None: noise_transformed = noise_transformed[:, ::down_sample_pre] print('Ready For Temporal Filters') ## Apply temporal filters #presumably these filters are reversed when convolve (like the normal case) so need to flip potentially when calculating the "STRF" for weights tFilter = temporalFilter() tFilter2 = np.repeat(tFilter, 3) / 3 #make wider filter tFilters = [tFilter, tFilter2] vocals_temporal_transformed = temporal_transform_list( vocals_transformed, tFilters) print('Vocals Temporally Filtered...') if noise is not None: noise_temporal_transformed = temporal_transform( noise_transformed, tFilters) print('Noise Temporally Filtered') #again re-evaluate if down sampled if down_sample: for i, vocal in enumerate(vocals_temporal_transformed): vocals_temporal_transformed[ i] = vocal[:, :: down_sample_post] #I guess this does a separate down sample after the temporal filters have been applied? if noise is not None: noise_temporal_transformed = noise_temporal_transformed[:, :: down_sample_post] ## Create Training Dataset samples = np.random.randint(num_vocals, size=num_samples) even_samples_check = np.sum(samples == 1) #Again, note above comment,I think this only really works when only two vocalizations which is the case for now while even_samples_check != np.round( num_samples / num_vocals ): #while samples are not even across vocalizations print('Ensuring equal presentation of both vocalizations') samples = np.random.randint(num_vocals, size=num_samples) even_samples_check = np.sum(samples == 1) print('Equal presentation of both vocalizations established') training_data = None initialized = False for i in tqdm(samples): if (not (initialized)): training_data = vocals_temporal_transformed[i] initialized = True else: training_data = np.concatenate( (training_data, vocals_temporal_transformed[i]), 1) if (gaps): min_gap = np.round( .05 * vocals_temporal_transformed[0].shape[1] ) #sets min range of gap as percentage of length of a single vocalizations max_gap = np.round( .5 * vocals_temporal_transformed[0].shape[1] ) #set max range of gap in same units as above training_data = np.concatenate( (training_data, np.zeros((training_data.shape[0], np.random.randint(min_gap, max_gap)))), 1) print('Data arranged...') if (apply_noise): while (noise_temporal_transformed[0].size < training_data[0].size): noise_temporal_transformed = np.hstack( (noise_temporal_transformed, noise_temporal_transformed)) training_data = training_data + noise_temporal_transformed[:, 0: training_data[ 0] . size] print('Applied Noise...') print('No Noise Applied...') print('Ready For SFA') ## Train SFA On Data (layer1, mean, variance, data_SS, weights) = getSF(training_data, 'Layer 1', transform=True) print('SFA Training Complete') ## Test Results samples = np.arange(num_vocals) testing_data = None initialized = False for i in tqdm(samples): if (not (initialized)): testing_data = vocals_temporal_transformed[i] initialized = True else: testing_data = np.concatenate( (testing_data, vocals_temporal_transformed[i]), 1) print('Data arranged...') if (test_noise): #NOTE: 2020-08-04 rolling old noise is not good enough, add code to add novel noise when ready. testing_data = testing_data + noise_temporal_transformed[:, 0: testing_data[ 0] .size] print('Applied Noise...') else: print('No Noise Applied...') print('Testing Data Ready') ## Apply SFA to Test Data, also toggles for using second layer test = testSF(testing_data, 'Layer 1', mean, variance, data_SS, weights) print('SFA Applied To Test Set') #old code related to adding a second layer. #test = np.vstack((test[:,5:], test[:,:-5])) #test = testSF(test, 'Layer 2', mean2, variance2, data_SS2, weights2) if run_nulls: labels = np.random.randint( 0, 2, test.shape[1]) #just put in random labels. else: labels = getlabels(vocals_temporal_transformed) ## Compare SFA With Baseline For Linear Classification #Updated this 2020-08-04 to use the other clasification test. #split and shuffle data n_split times. Split to train on 1% and test on remainder the_cv = ShuffleSplit(n_splits=30, test_size=0.99) print('SFA Based Classifier with ', classifier_features, ' features') cv_sfa = cross_validate(classifier_SFA, test.T, labels, cv=the_cv) SFA_save_score[a_round, iteration] = np.mean( cv_sfa['test_score'] ) #take average of all CV folds as the score for that round for that SNR print('Baseline Classifier with ', baseline_features, ' features') cv_baseline = cross_validate(classifier_baseline, testing_data.T, labels, cv=the_cv) Baseline_save_score[a_round, iteration] = np.mean( cv_baseline['test_score'] ) #same thing for the baseline classifier, this should have consistent performance, but can use the variance present in this as a check on the classifier #not returning weights for now/will write another file or modify things when running other examples. return SFA_save_score, Baseline_save_score
for i in range(0, training_set[0].size): #grab all vocal files in that training set (10 in our case as of 2020-08-18) #Note training set is now [set][vocal][0][0] #2020-08-25 below works t_voc = vocal_foldername / training_set[training_group][i][0][0] t_voc = PureWindowsPath(t_voc) training_files.append((t_voc)) 'from here on we have to be careful to make sure things match up properly' #set number of vocals to the training vocals since this is called throughout code. May have to have another variable for testing vocals though num_vocals = len(training_files) num_test_vocals = len(vocal_files) #set up gammatone filter gfb = g.GammatoneFilterbank(order=1, density = 1.0, startband = -21, endband = 21, normfreq = 2200) down_sample = True #down sample stimulus to help with RAM issues and general dimensionality issues. I believe mostly reduces resolution of frequency down_sample_pre = 2 #Factor by which to reduce Fs by (e.g. 10 reduces Fs by one order of magnitude) down_sample_post = 2 #Factor by which to reduce Fs after applying filters ##Training and testing data parameters num_samples = num_vocals * 1 gaps = True #toggle whether there can be gaps between presentation of each stimulus
#'Stimulus_Set_1/AM_Stimulus_7_4.wav','Stimulus_Set_1/AM_Stimulus_8_4.5.wav','Stimulus_Set_1/AM_Stimulus_9_5.wav' ] #set names of files to be played/trained and tested on #bump examples up to 9 again vocal_files_test = [ 'Monkey_Calls/AG493B.wav', 'Monkey_Calls/CO1Z67.wav', 'Monkey_Calls/CS1E54.wav' ] #['Stimulus_Set_1/AM_Stimulus_1_1.wav', list_of_names[a_stimulus] ] noise_file = 'Clutter_Stimuli/ClutterChorus_10vocalizations.wav' #'bp_w_noise.wav' #file to load for noise num_vocals_train = len( vocal_files_train ) #for use later, get number of unique stimulus files loaded num_vocals_test = len(vocal_files_test) ## Parameters for vocalization and noise preprocessing signal_to_noise_ratio = 50 #unclear if scales on moment by moment amplitude or by power (i.e. intergrated energy across frequencies) gfb = g.GammatoneFilterbank( order=1, density=1.0, startband=-21, endband=21, normfreq=2200 ) #sets up parameters for our gammatone filter model of the cochlea. #Need to look at documentation to figure out exactly how these parameters work , but normfreq at least seems to be central frequency from #which the rest of the fitler a distributed (accoruding to startband and endband) plot_gammatone_transformed = False #toggle to plot output of gammatone filtered stimulus plot_temporal_filters = False #toggle to plot temporal filters (i.e. temporal component of STRF) plot_temporal_transformed = False #toggle to plot signal after being gammatone filtered and temporally filtered down_sample = True #down sample stimulus to help with RAM issues and general dimensionality issues. I believe mostly reduces resolution of frequency down_sample_pre = 10 #Factor by which to reduce Fs by (e.g. 10 reduces Fs by one order of magnitude) down_sample_post = 10 #Factor by which to reduce Fs after applying filters #(2019-09-18 trying removing either of the down sampling and it caused memory errors, meaning I don't fully understand how this is working) ## Parameters for training data num_samples = num_vocals_train * 15 #choose how many times you see each stimulus gaps = True #toggle whether there can be gaps between presentation of each stimulus apply_noise = False #toggle for applying noise
def run_SFA(vocal_foldername, vocal_pair, training_set, run_nulls): #Save SFA classification accuracy and the baseline model score SFA_save_score = np.zeros((5, training_set.size)) Baseline_save_score = np.zeros((5, training_set.size)) #grab test file vocals since these are the same across training groups voc1 = vocal_foldername / vocal_pair[0][0] voc2 = vocal_foldername / vocal_pair[1][0] #convert to windows path i.e using \ instead of / voc1 = PureWindowsPath(voc1) voc2 = PureWindowsPath(voc2) #set up list of vocal files to load for testing data vocal_files = [voc1, voc2] for training_group in range(0, training_set.size): #for each training set for a_round in range( 0, 5): #rounds are for averaging for each training group print(a_round) #Unfortunately don't really have an elegent solution for setting up all of the training files we want to pull...so trying below training_files = list() for i in range( 0, training_set[0].size ): #grab all vocal files in that training set (10 in our case as of 2020-08-18) #Note training set is now [set][vocal][0][0] #2020-08-25 below works t_voc = vocal_foldername / training_set[training_group][i][0][0] t_voc = PureWindowsPath(t_voc) training_files.append((t_voc)) 'from here on we have to be careful to make sure things match up properly' #set number of vocals to the training vocals since this is called throughout code. May have to have another variable for testing vocals though num_vocals = len(training_files) num_test_vocals = len(vocal_files) #set up gammatone filter gfb = g.GammatoneFilterbank(order=1, density=1.0, startband=-21, endband=21, normfreq=2200) down_sample = True #down sample stimulus to help with RAM issues and general dimensionality issues. I believe mostly reduces resolution of frequency down_sample_pre = 2 #Factor by which to reduce Fs by (e.g. 10 reduces Fs by one order of magnitude) down_sample_post = 2 #Factor by which to reduce Fs after applying filters ##Training and testing data parameters num_samples = num_vocals * 1 gaps = True #toggle whether there can be gaps between presentation of each stimulus #skipping noise parameters for now #Set up classifiers classifier_baseline = LinearSVC( max_iter=10000000, tol=0.001 ) #Perceptron(max_iter = 10000, tol = 0.001) #from scikit (presumably) #set up Perceptron classifier classifier_SFA = LinearSVC( max_iter=10000000, tol=0.001) #Perceptron(max_iter = 10000, tol = 0.001) #trying dropping classifier down to 5 featuers used classifier_features = 5 #how many features from SFA SFA classifer gets to use baseline_features = 'all' #how many features the Perceptron by itself gets to use ## Load in files vocalizations = get_data( training_files ) #get list object where each entry is a numpy array of each vocal file testvocalizations = get_data(vocal_files) print('Vocalizations Loaded...') ## Apply Gammatone Transform to training and test vocals_transformed = gamma_transform_list( vocalizations, gfb ) #does what is says on the tin: applies gamma transform to list of numpy arrays testvocals_transformed = gamma_transform_list( testvocalizations, gfb) print('Vocalizations Transformed...') ## Down sample for computation tractablility if down_sample: for i, vocal in enumerate(vocals_transformed): vocals_transformed[i] = vocal[:, ::down_sample_pre] for i, vocal in enumerate(testvocals_transformed): testvocals_transformed[i] = vocal[:, ::down_sample_pre] print('Ready For Temporal Filters') ## Apply temporal filters #2020-07-21 double check that these filters are right and are not producing an abnormal offset between the narrower and longer filter #presumably these filters are reversed when convolve (like the normal case) so need to flip potentially when calculating the "STRF" for weights tFilter = temporalFilter() tFilter2 = np.repeat(tFilter, 3) / 3 #make wider filter tFilters = [tFilter, tFilter2] vocals_temporal_transformed = temporal_transform_list( vocals_transformed, tFilters) testvocals_temporal_transformed = temporal_transform_list( testvocals_transformed, tFilters) print('Vocals Temporally Filtered...') if down_sample: for i, vocal in enumerate(vocals_temporal_transformed): vocals_temporal_transformed[ i] = vocal[:, ::down_sample_post] for i, vocal in enumerate(testvocals_temporal_transformed): testvocals_temporal_transformed[ i] = vocal[:, ::down_sample_post] samples = np.random.choice( num_vocals, num_samples, replace=False ) #Have to switch to using random.choice so can remove replacement. This means we can remove while loop too print( 'Equal presentation of vocalizations established' ) #note this mainly works because we are presenting each vocal once. If that was not the case we would have to set up some kind of loop or additional code training_data = None initialized = False for i in tqdm(samples): if (not (initialized)): training_data = vocals_temporal_transformed[i] initialized = True else: training_data = np.concatenate( (training_data, vocals_temporal_transformed[i]), 1) if (gaps): min_gap = np.round( .05 * vocals_temporal_transformed[0].shape[1] ) #sets min range of gap as percentage of length of a single vocalizations max_gap = np.round( .5 * vocals_temporal_transformed[0].shape[1] ) #set max range of gap in same units as above training_data = np.concatenate( (training_data, np.zeros((training_data.shape[0], np.random.randint(min_gap, max_gap)))), 1) print('Data arranged...') print('No Noise Applied...') print('Ready For SFA') ## Train SFA On Data (layer1, mean, variance, data_SS, weights) = getSF(training_data, 'Layer 1', transform=True) print('SFA Training Complete') ## Test Results 'NOTE: 220-08-25 This is the section we need to edit and check since training and test do not match' samples = np.arange(num_test_vocals) testing_data = None initialized = False for i in tqdm(samples): if (not (initialized)): testing_data = testvocals_temporal_transformed[i] initialized = True else: testing_data = np.concatenate( (testing_data, testvocals_temporal_transformed[i]), 1) print('Data arranged...') print('No Noise Applied...') print('Testing Data Ready') ## Apply SFA to Test Data, also toggles for using second layer test = testSF(testing_data, 'Layer 1', mean, variance, data_SS, weights) print('SFA Applied To Test Set') ## If null runs, shuffle the labels. Note can adjust this like we did for equal presentation if we end up with more than two test vocals. if run_nulls: labels = np.random.randint( 0, 2, test.shape[1]) #just put in random labels. else: labels = getlabels(vocals_temporal_transformed) the_cv = ShuffleSplit(n_splits=30, test_size=0.99) print('SFA Based Classifier with ', classifier_features, ' features') #add a cv loop to pin down variance cv_sfa = cross_validate(classifier_SFA, test.T, labels, cv=the_cv) print(cv_sfa['test_score']) print('Mean CV ', np.mean(cv_sfa['test_score'])) SFA_save_score[a_round, training_group] = np.mean(cv_sfa['test_score']) print('Baseline Classifier with ', baseline_features, ' features') cv_baseline = cross_validate(classifier_baseline, testing_data.T, labels, cv=the_cv) print(cv_baseline['test_score']) print('Mean CV ', np.mean(cv_baseline['test_score'])) Baseline_save_score[a_round, training_group] = np.mean( cv_baseline['test_score'] ) #classifier_baseline.score(testing_data.T,labels) print('') print('') print(SFA_save_score) print('') print(Baseline_save_score) #not returning weights for now/will write another file or modify things when running other examples. return SFA_save_score, Baseline_save_score