Использование NLTK для классификации документов по вопросам содержания веб-сайтов с BeautifulSoup и NaiveBayes

У меня есть проект на Python 2.7, в котором я хочу классифицировать сайты по их содержанию. У меня есть база данных, в которой я размещаю многочисленные URL веб-сайтов и связанные с ними категории. Есть много категорий (= ярлыки), и я хочу классифицировать новые сайты в соответствующие категории в зависимости от их содержания. Я следовал приведенному здесь учебнику / примеру классификации NLTK, но столкнулся с некоторыми проблемами, которые я не могу объяснить.

Вот схема процесса, который я использую:

  1. Используйте MySQLdb для получения категории, связанной с заданным URL-адресом веб-сайта. Это будет использоваться при извлечении данных (контента) из URL для сопряжения их с категорией (= метка) сайта.
  2. Используйте функцию getSiteContent(site) для извлечения контента с сайта

Вышеприведенная функция выглядит так:

def getSiteContent(site):
    try:
        response = urllib2.urlopen(site, timeout = 1)
        htmlSource = response.read()
    except Exception as e: # <=== some websites may be inaccessible as list isn't up-to-date
        global errors
        errors += 1
        return ''

    soup = BeautifulSoup(htmlSource)
    for script in soup.find_all('script'):
        script.extract()

    commonWords = set(stopwords.words('english'))
    commonWords.update(['function', 'document', 'window', 'functions',     'getElementsByTagName', 'parentNode', 'getDocumentById', 'javascript', 'createElement',     'Copyright', 'Copyrights', 'respective', 'owners', 'Contact Us', 'Mobile Version', 'FAQ',     'Privacy Policy', 'Terms of Service', 'Legal Disclaimer' ])

    text = soup.get_text()

    # Remove ',', '/', '%', ':'
    re.sub(r'(\\d+[,/%:]?\\d*)', '', text)
    # Remove digits
    re.sub(r'\d+', '', text)
    # Remove non-ASCII
    re.sub(r'[^\x00-\x7F]',' ', text)
    # Remove stopwords
    for word in commonWords :
        text = text.replace(' '+word+' ', ' ')

    # Tokenize the site content using NLTK
    tokens = word_tokenize(text)

    # We collect some word statistics, i.e. how many times a given word appears in the     text
    counts = defaultdict(int)
    for token in tokens:
        counts[token] += 1

    features = {}
    # Get rid of words that appear less than 3 times
    for word in tokens:
        if counts[word] >= 3 :
            features['count(%s)' % word] = counts[word]

    return features 

Когда все вышеперечисленное сделано, я делаю следующее:

train = getTrainingSet(n)
random.shuffle(train)

Где n - количество сайтов, с которыми я хочу обучить свою модель.

После этого я делаю:

feature_set = []
count = 0
for (site, category) in train:
    result = getSiteContent(site)
    count += 1
    if result != '':
        print "%d. Got content for %s" % (count, site)
        feature_set.append((result, category))
    else  :
        print "%d. Failed to get content for %s" % (count, site)

Операторы печати в основном предназначены для отладки. После того как я сделаю выше, feature_set содержит что-то похожее на следующее:

print feature_set
[({u'count(import)': 22, u'count(maxim)': 22, u'count(Maxim)': 5, u'count(css)': 22, u'count(//www)': 22, u'count(;)': 22, u'count(url)': 22, u'count(Gift)': 3, u"count('')": 44, u'count(http)': 22, u'count(&)': 3, u'count(ng16ub)': 22, u'count(STYLEThe)': 3, u'count(com/modules/system/system)': 4, u'count(@)': 22, u'count(?)': 22}, 'Arts & Entertainment'), ({u'count(import)': 3, u'count(css)': 3, u'count(\u05d4\u05d9\u05d5\u05dd)': 4, u'count(\u05de\u05d9\u05dc\u05d5\u05df)': 6, u'count(;)': 3, u'count(\u05e2\u05d1\u05e8\u05d9)': 4, u'count(\u05d0\u05ea)': 3, u'count(\u05de\u05d5\u05e8\u05e4\u05d9\u05e7\u05e1)': 6, u"count('')": 6, u'count(\u05d4\u05d5\u05d0)': 3, u'count(\u05e8\u05d1\u05de\u05d9\u05dc\u05d9\u05dd)': 3, u'count(ver=01122014_4)': 3, u'count(|)': 4, u'count(``)': 4, u'count(@)': 3, u'count(?)': 7}, 'Miscellaneous')]

После этого я пытаюсь обучить свой классификатор, а затем запустить его с тестовыми данными, которые я извлекаю из feature_set

train_set, test_set = feature_set[len(train)/2:], feature_set[:len(train)/2]
print "Num in train_set: %d" % len(train_set)
print "Num in test_set: %d" % len(test_set)
classifier = nltk.NaiveBayesClassifier.train(train_set) # <=== classified declared on train_set
print classifier.show_most_informative_features(5)
print "=== Classifying a site ==="
print classifier.classify(getSiteContent("http://www.mangaspoiler.com"))
print "Non-working sites: %d" % errors
print "Classifier accuracy: %d" % nltk.classify.accuracy(classifier, test_set)

Это почти то же самое, что и учебник на веб-сайте документации NLTK. Тем не менее, результаты следующие (учитывая набор из 100 веб-сайтов):

$ python classify.py
Num in train_set: 23
Num in test_set: 50
Most Informative Features
            count(Pizza) = None           Arts & : Techno =      1.0 : 1.0
None
=== Classifying a site ===
Technology & Computing
Non-working sites: 27
Classifier accuracy: 0

Теперь, очевидно, есть несколько проблем с этим:

  1. Токены слова содержат символы Юникода, такие как \u05e2\u05d1\u05e8\u05d9, так как кажется, что регулярное выражение для их удаления работает, только если они автономны. Это небольшая проблема.

  2. Большая проблема в том, что даже когда я print feature_set, жетоны слова отображаются как u'count(...)' = # в отличие от 'count(...)' = #, Я думаю, что это может быть более серьезной проблемой и частью того, почему мой классификатор не работает.

  3. Классификатор, очевидно, терпит крах как какой-то момент. Точность указана как 0 даже если я введу весь свой набор данных в классификатор, что кажется крайне маловероятным.

  4. Most Informative Features функция говорит, что count(Pizza) = None, Код, где я заявляю defaultdict(int)Однако требуется, чтобы каждая запись была связана с количеством появлений в тексте.


Я в растерянности относительно того, почему это происходит. Насколько я могу судить, мои данные структурированы идентично данным, которые документация NLTK использует в своем руководстве на веб-сайте, на который я ссылался в верхней части этого вопроса. Если кто-то, кто работал с NLTK, видел такое поведение раньше, я был бы очень признателен за любые советы о том, что я могу делать неправильно.

1 ответ

Здесь, вероятно, много ошибок, но здесь стоит первая и самая очевидная:

Точность указана как 0 даже если я передам весь свой набор данных в классификатор

Это не указано как 0.0? Похоже, что-то там, что должно быть float является int, Я подозреваю, что вы делаете разделение в какой-то момент для нормализации, и int/int не превращается в float,

При построении таблицы подсчета добавьте 1.0 для каждого счета, а не 1, Это исправит источник проблемы, и исправления будут уменьшаться.

Если кажется странным считать документы поплавками, считайте каждый счет измерением в научном смысле слова, а не представлением отдельного документа.

Другие вопросы по тегам