Search code examples
pythonmachine-learningnlpnltk

Bleu score in python from scratch


After watching Andrew Ng's video about Bleu score I wanted to implement one from scratch in python. I wrote the code full in python with numpy sparingly. This is the full code

import numpy as np

def n_gram_generator(sentence,n= 2,n_gram= False):
    '''
    N-Gram generator with parameters sentence
    n is for number of n_grams
    The n_gram parameter removes repeating n_grams 
    '''
    sentence = sentence.lower() # converting to lower case
    sent_arr = np.array(sentence.split()) # split to string arrays
    length = len(sent_arr)

    word_list = []
    for i in range(length+1):
        if i < n:
            continue
        word_range = list(range(i-n,i))
        s_list = sent_arr[word_range]
        string = ' '.join(s_list) # converting list to strings
        word_list.append(string) # append to word_list
        if n_gram:
            word_list = list(set(word_list))
    return word_list

def bleu_score(original,machine_translated):
    '''
    Bleu score function given a orginal and a machine translated sentences
    '''
    mt_length = len(machine_translated.split())
    o_length = len(original.split())

    # Brevity Penalty 
    if mt_length>o_length:
        BP=1
    else:
        penality=1-(mt_length/o_length)
        BP=np.exp(penality)

    # calculating precision
    precision_score = []
    for i in range(mt_length):
        original_n_gram = n_gram_generator(original,i)
        machine_n_gram = n_gram_generator(machine_translated,i)
        n_gram_list = list(set(machine_n_gram)) # removes repeating strings

        # counting number of occurence 
        machine_score = 0
        original_score = 0
        for j in n_gram_list:
            machine_count = machine_n_gram.count(j)
            original_count = original_n_gram.count(j)
            machine_score = machine_score+machine_count
            original_score = original_score+original_count

        precision = original_score/machine_score
        precision_score.append(precision)
    precisions_sum = np.array(precision_score).sum()
    avg_precisions_sum=precisions_sum/mt_length
    bleu=BP*np.exp(avg_precisions_sum)
    return bleu

if __name__ == "__main__":
    original = "this is a test"
    bs=bleu_score(original,original)
    print("Bleu Score Original",bs)

I tried to test my score with nltk's

from nltk.translate.bleu_score import sentence_bleu
reference = [['this', 'is', 'a', 'test']]
candidate = ['this', 'is', 'a', 'test']
score = sentence_bleu(reference, candidate)
print(score)

The problem is my bleu score is about 2.718281 and nltk's is 1. What am I doing wrong?

Here are some possible reason's:

1) I calculated ngrams with respect to the length of the machine translated sentence. Here from 1 to 4

2) n_gram_generator function which I wrote myself and not sure about its accuracy

3) Some how I used wrong function or miscalculated bleu score

Can some one look my code up and tell me where I did the mistake?


Solution

  • Your bleu score calculation is wrong. Issue:

    • You have to use clipped precision
    • sklearn uses weights for each n gram
    • sklearn uses ngrams for n = 1,2,3,4

    Corrected code

    def bleu_score(original,machine_translated):
        '''
        Bleu score function given a orginal and a machine translated sentences
        '''
        mt_length = len(machine_translated.split())
        o_length = len(original.split())
    
        # Brevity Penalty 
        if mt_length>o_length:
            BP=1
        else:
            penality=1-(mt_length/o_length)
            BP=np.exp(penality)
    
        # Clipped precision
        clipped_precision_score = []
        for i in range(1, 5):
            original_n_gram = Counter(n_gram_generator(original,i))
            machine_n_gram = Counter(n_gram_generator(machine_translated,i))
    
            c = sum(machine_n_gram.values())
            for j in machine_n_gram:
                if j in original_n_gram:
                    if machine_n_gram[j] > original_n_gram[j]:
                        machine_n_gram[j] = original_n_gram[j]
                else:
                    machine_n_gram[j] = 0
    
            #print (sum(machine_n_gram.values()), c)
            clipped_precision_score.append(sum(machine_n_gram.values())/c)
    
        #print (clipped_precision_score)
    
        weights =[0.25]*4
    
        s = (w_i * math.log(p_i) for w_i, p_i in zip(weights, clipped_precision_score))
        s = BP * math.exp(math.fsum(s))
        return s
    
    original = "It is a guide to action which ensures that the military alwasy obeys the command of the party"
    machine_translated = "It is the guiding principle which guarantees the military forces alwasy being under the command of the party"
    
    print (bleu_score(original, machine_translated))
    print (sentence_bleu([original.split()], machine_translated.split()))
    

    Output:

    0.27098211583470044
    0.27098211583470044