diff --git a/README.md b/README.md index 5aa4cdc..3d80985 100644 --- a/README.md +++ b/README.md @@ -1,12 +1,12 @@ # Goodness of Pronunciation (GoP) -This code reflects the work described in the **[INTERSPEECH 2019](https://www.interspeech2019.org/)** published paper on **["An improved goodness of pronunciation (GoP) measure for pronunciation evaluation with DNN-HMM system considering HMM transition probabilities"](https://www.isca-speech.org/archive/Interspeech_2019/pdfs/2363.pdf)**. +This code reflects the work described in the **[INTERSPEECH 2019](https://www.interspeech2019.org/)** published paper on **["An improved goodness of pronunciation (GoP) measure for pronunciation evaluation with DNN-HMM system considering HMM transition probabilities"](https://www.isca-speech.org/archive/pdfs/interspeech_2019/sudhakara19_interspeech.pdf)**. # Requirements : * Python (tested with v.2.7.5 & v.3.5.7). -* Kaldi ASR toolkit (for documentation checkout : http://kaldi-asr.org/) considering acoustic models trained with _nnet2_ (Dan's recipe) (tested with nnet2 & nnet3). +* Kaldi ASR toolkit (for documentation checkout : http://kaldi-asr.org/) considering acoustic models trained with _nnet2_ (Dan's recipe) (tested with nnet2 & nnet3) on LibriSpeech. # How to run the code : -Run the below code (**_prop_gop_eqn.py_**) to compute the score using the proposed GoP formulation by passing **_alignment_infile.txt_** and **_posterior_infile.ark_** generated for a given learner's utterance. +Run the below code (**_prop_gop_eqn.py_**) to compute the score using the proposed GoP formulation by passing **_alignment_infile.txt_** and **_posterior_infile.ark_** generated for a single learner's utterance. ```python python prop_gop_eqn.py posterior_infile.ark alignment_infile.txt gop_outfile.txt ``` @@ -14,8 +14,11 @@ python prop_gop_eqn.py posterior_infile.ark alignment_infile.txt gop_outfile.txt * The **_posterior_infile.ark_** file contains the frame level posterior-probabilities of the learner's uttered speech (.wav file) and this is obtained using **_nnet_am_compute.cc_**. * The **_gop_outfile.txt_** file contains the score for each phoneme. +How to generate the input files: + + **NOTE** : -* The above python script requires a lookup table to generate the scores for an acoustic model as discussed in the paper, which can be generated using the following code : +* The above Python script requires a lookup table to generate the scores for an acoustic model as discussed in the paper, which can be generated using the following code : ```shell ./gen_lookup_table.sh ``` diff --git a/prop_gop_eqn.py b/prop_gop_eqn.py index da0baee..4e424e2 100644 --- a/prop_gop_eqn.py +++ b/prop_gop_eqn.py @@ -9,72 +9,86 @@ import pandas as pd import numpy as np -path = os.getcwd(); - +path = os.getcwd(); + # Modifying posterior.ark to posterior.txt -var1 = [path + '/reqd_files/' + sys.argv[1]]; -subprocess.call(['bash', 'modify_post.sh', str(var1[0])]); - -# Creating segment information list, aligned phones list & transition_id's list -var2 = [path + '/reqd_files/' + sys.argv[2]]; -subprocess.call(['bash', 'extract_from_alignments.sh', str(var2[0])]); - +#var1 = [path + '/reqd_files/'+ sys.argv[1]] + +var1 = [sys.argv[1]] +subprocess.call(['bash', 'modify_post.sh', str(var1[0])]) + +# Creating segment information list, aligned phones list & transition_id's list +#var2 = [path +'/reqd_files/'+sys.argv[2]] +var2 = [sys.argv[2]] +subprocess.call(['bash', 'extract_from_alignments.sh', str(var2[0])]) + with open(path + '/reqd_files/tmp_segments.txt','r') as f: - x = f.readlines(); -number_of_segments = [int(tmp.split(' ')[0]) for tmp in x]; - + x = f.readlines() +number_of_segments = [int(tmp.split(' ')[0]) for tmp in x] +print(number_of_segments) with open(path + '/reqd_files/tmp_t_ids.txt','r') as f: - x = f.readlines(); -transition_id = [int(tmp.rstrip().split(' ')[0]) for tmp in x]; + x = f.readlines() +transition_id = [int(tmp.rstrip().split(' ')[0]) for tmp in x] with open(path + '/reqd_files/tmp_phones.txt','r') as f: - x = f.readlines(); -aligned_phones = [tmp.rstrip().split(' ')[0] for tmp in x]; + x = f.readlines() +aligned_phones = [tmp.rstrip().split(' ')[0] for tmp in x] with open(path + '/reqd_files/posterior.txt','r') as f: - x = f.readlines(); -posterior = [tmp.rstrip().split(' ') for tmp in x]; -num_of_senones = len(posterior[0]); -total_num_frames = len(posterior); - + x = f.readlines() +posterior = [tmp.rstrip().split(' ') for tmp in x] +num_of_senones = len(posterior[0]) +total_num_frames = len(posterior) +#print(total_num_frames,num_of_senones) with open(path + '/reqd_files/lookup_table.txt','r') as f: - x = f.readlines(); -lookup_tab = [tmp.rstrip().split(' ') for tmp in x]; - -series = pd.Series(number_of_segments); -cum_number_of_segments_tmp = series.cumsum(); -cum_number_of_segments=cum_number_of_segments_tmp.tolist(); -cum_number_of_segments.insert(0,0); - -phone_score = []; + x = f.readlines() +lookup_tab = [tmp.rstrip().split(' ') for tmp in x] -# Code for Proposed GoP formulation : +series = pd.Series(number_of_segments) +cum_number_of_segments_tmp = series.cumsum() +cum_number_of_segments=cum_number_of_segments_tmp.tolist() +cum_number_of_segments.insert(0,0) +print(cum_number_of_segments) +phone_score = [] +phone_post=[]#<--- +# Code for Proposed GoP formulation : for x in range(len(number_of_segments)): - + req_t_id=transition_id[cum_number_of_segments[x]:(cum_number_of_segments[x+1])]; - req_t_id.sort(); - score=0.0; - + req_t_id.sort() + #print("Segment num",x,req_t_id) + score=0.0 + tmp_phone_post=[] # <-- for y in range(len(req_t_id)-1): - - tmp_prob = float(lookup_tab[req_t_id[y]-1][2]); - tmp_pdf = int(lookup_tab[req_t_id[y]-1][1]); - tmp_post = float(posterior[cum_number_of_segments[x]+y][tmp_pdf]); - score = score + math.log(tmp_prob) + math.log(tmp_post); - - tmp_pdf = int(lookup_tab[req_t_id[-1]-1][1]); - tmp_post = float(posterior[cum_number_of_segments[x+1]-1][tmp_pdf]); - score = (score + math.log(tmp_post) + float(number_of_segments[x]-1)*math.log(num_of_senones)) / float(number_of_segments[x]); - phone_score.append(score); + + tmp_prob = float(lookup_tab[req_t_id[y]-1][2]) + tmp_pdf = int(lookup_tab[req_t_id[y]-1][1]) + tmp_post = float(posterior[cum_number_of_segments[x]+y][tmp_pdf]) + #print("Senone_ID/Phone no.",x,"Transitions",cum_number_of_segments[x]+y,"PDF",tmp_pdf,tmp_post) + tmp_phone_post.append(tmp_post)# <----- + score = score + math.log(tmp_prob) + math.log(tmp_post) + tmp_pdf = int(lookup_tab[req_t_id[-1]-1][1]) + tmp_post = float(posterior[cum_number_of_segments[x+1]-1][tmp_pdf]) + tmp_phone_post.append(tmp_post) #<---------- + #print("Senone_ID/Phone no.",x,"Transitions",cum_number_of_segments[x+1]-1,"PDF",tmp_pdf,tmp_post,end="\n") + score = (score + math.log(tmp_post) + float(number_of_segments[x]-1)*math.log(num_of_senones)) / float(number_of_segments[x]) + phone_post.append(tmp_phone_post) + phone_score.append(score) # Displaying the scores : -print('Forced aligned phonemes : '); -print(aligned_phones); -print('GOP formulated score of each phoneme : '); -print(phone_score); - -# The phoneme_list.txt file contains phoneme's in the 1st column and GoP formulated scores in the 2nd column +print('Forced aligned phonemes : ') +print(aligned_phones) +print('GOP formulated score of each phoneme : ') +print(phone_score) +#print('Posterior probability of each phoneme') +#print(phone_post) + +# The phoneme_list.txt file contains phoneme's in the 1st column and GoP formulated scores in the 2nd column f = open(sys.argv[3], 'w') for i in range(len(phone_score)): f.write("%s %f\n" % (aligned_phones[i], phone_score[i])) f.close() +f=open(sys.argv[3].strip('.txt')+'_phone_posteriors.txt','w+') +for i in range(len(phone_score)): + f.write("%s %s\n" % (aligned_phones[i],str(phone_post[i]).rstrip(']').lstrip('['))) +f.close()