Построить (векторизатор.idf_) функцию Sklearn с нуля

#python #scikit-learn #nlp #tfidfvectorizer

Вопрос:

Я реализую функцию sklearn (vectorizer.idf_) с нуля и сравниваю результат. Так что для данного корпуса, скажем,

РЕАЛИЗАЦИЯ SKLEARN:-

 corpus = [
     'this is the first document',
     'this document is the second document',
     'and this is the third one',
     'is this the first document',
]

from sklearn.feature_extraction.text import TfidfVectorizer
vectorizer = TfidfVectorizer()
vectorizer.fit(corpus)
skl_output = vectorizer.transform(corpus)

print(vectorizer.get_feature_names())

OUTPUT:- ['and', 'document', 'first', 'is', 'one', 'second', 'the', 'third', 'this']

print(vectorizer.idf_)

OUTPUT:- [1.91629073 1.22314355 1.51082562 1.         1.91629073 1.91629073
 1.         1.91629073 1.        ]
 

Мой пользовательский код выглядит следующим образом, где у меня есть метод fit (), который преобразует корпус в словарь, и функция idf (), в которой будет подсчитываться, сколько раз встречается конкретное слово в данном корпусе, и функция transform (), в которой я вычисляю значения idf.

 corpus = [
     'this is the first document',
     'this document is the second document',
     'and this is the third one',
     'is this the first document',
]

def fit(dataset):
    storage_set = set()
    if isinstance(dataset,list):
        for document in dataset:
            for word in document.split(" "):
                storage_set.add(word)
        storage_set = sorted(list(storage_set))
        vocab = {j:i for i,j in enumerate(storage_set)}
        #Idf_values_of_all_unique_words=IDF(dataset,storage_set)
        
    #print(list(storage_set))
    return vocab

vocab =  fit(corpus)
print(vocab)

OUTPUT:- {'and': 0, 'document': 1, 'first': 2, 'is': 3, 'one': 4, 'second': 5, 'the': 6, 'third': 7, 'this': 8}

def idf(dataset,word):
    count=0
    for row in dataset:
        if word in row:
            count =1            
    return count

def transform(dataset,vocab):
    row = []
    col = []
    values = []
    idf_value=[]
    
    for ibx,document in enumerate(dataset):
        word_freq = dict(Counter(document.split()))
        for word, freq in word_freq.items():
            col_index = vocab.get(word,-1)
            if col_index != -1:
                if len(word)<2:
                    continue
                col.append(col_index)
                row.append(ibx)
                
                term_freq = freq/(len(document)) # the number of times a word occured in a document
                idf_ = 1 math.log((1 len(dataset))/(1 idf(dataset,word)))
                
                
                values.append((term_freq) * (idf_))
                
                idf_value.append(idf_)
    print(idf_value)

OUTPUT:- [1.0, 1.0, 1.0, 1.5108256237659907, 1.2231435513142097, 1.0, 1.2231435513142097, 1.0, 1.0, 1.916290731874155, 1.916290731874155, 1.0, 1.0, 1.0, 1.916290731874155, 1.916290731874155, 1.0, 1.0, 1.0, 1.5108256237659907, 1.2231435513142097]
 

поэтому, если я сравниваю вывод sklearn для оценки idf, это массив из 9 значений, так как в корпусе 9 разных слов, но я получаю массив размером 22. Может ли кто-нибудь помочь мне понять, где я делаю это неправильно.

Комментарии:

1. используйте print() для просмотра значений переменных и того, какие строки кода выполняются, и сравните это с расчетами на бумаге.

2. у вас есть вложенные for циклы-первые запуски 4 раза (количество документов) , внутренние запуски 9 раз (количество слов), поэтому он может создать даже 36 элементов. Это может означать, что вы неправильно рассчитали его. Вероятно, вы сохраняете каждое значение отдельно — для каждого документа разделенное значение. Но для каждого слова вы должны суммировать значения из всех документов. возможно, вам следует сохранить результат как values[word] = [result1, result2] и позже использовать их все для расчета (term_freq) * (idf_)

Ответ №1:

Для его расчета idf_ требуется всего один цикл.

 def transform(dataset, vocab):
    idf_value = []

    dataset_len = len(dataset)

    for word in vocab:
        idf_ = 1   math.log( (1   dataset_len) / (1   idf(dataset, word)) )
        idf_value.append(idf_)
                
    print(idf_value)
 

Потому что вы запускаете его в другом цикле, поэтому append() добавляете одно и то же значение много раз.

Таким образом, вы можете сначала рассчитать idf_ все, чтобы добавить их в список.

Или вы должны сохранить idf_ как словарь word: idf_ , а затем добавить то же самое снова idf_ , это не составит проблемы.


Этот код дает мне те же результаты, что и vectorizer.idf_

Это дает почти тот же результат, skl_output что и (я должен умножить на 10), но несколько значений отличаются — и я не знаю, почему.

 def transform(dataset, vocab):
    values = []
    idf_value=[]

    dataset_len = len(dataset)

    for word in vocab:
        idf_ = 1   math.log( (1   dataset_len) / (1   idf(dataset, word)) )
        idf_value.append(idf_)

    print(idf_value)
        
    for doc_idx, document in enumerate(dataset):
        document_len = len(document)
        word_freq = dict(Counter(document.split()))

        for word, freq in word_freq.items():
            word_idx = vocab[word]
            
            term_freq = freq/document_len
            idf_ = 1   math.log( (1   dataset_len) / (1   idf(dataset, word)) )
            
            values.append( ((doc_idx, word_idx), term_freq * idf_ * 10))
            
    for item in values:
        print(item)
 

Редактировать:

Лучше использовать split(' ') , когда вы проверяете

     if word in row.split(' '):
 

потому "is" in "this" что дает True , но тебе нужно False
и "is" in ["this"] дает правильно False