________fear_of_underscores

четверг, 13 сентября 2012 г.

Извлечение ключевых слов текста с помощью TF-IDF метрики

Что такое TF-IDF рассказано в этих прекрасных видео с Coursera (Web Intelligence and Big Data):
TF-IDF
TF-IDF Example

Я же покажу реализацию извлечения ключевых слов с помощью этой метрики на F#:
#if INTERACTIVE
#r @"..\packages\HtmlAgilityPack.1.4.6\lib\Net45\HtmlAgilityPack.dll"
#endif

open System
open System.Collections.Generic
open System.Collections.Concurrent
open System.Linq
open System.Text
open System.Text.RegularExpressions
open System.IO
open System.Net
open Microsoft.FSharp.Control.WebExtensions
open HtmlAgilityPack

let makeTFMap (text : string array) =
    let dict = new Dictionary<_, _>()
    for word in text do
        if dict.ContainsKey word then
            dict.[word] <- dict.[word] + 1
        else
            dict.Add(word, 1)
    dict

let IDF (uniqueTokens : string array) =
    let logWebSizeEstimate = Math.Log(5e10, 2.)
    let dict = new ConcurrentDictionary<_, _>()
    seq {
        for word in uniqueTokens ->
            async {
                let req = WebRequest.Create(@"http://www.google.com/search?q=" + word) :?> HttpWebRequest
                req.Timeout <- 5000
                req.UserAgent <-
                    "Mozilla/4.1 (compatible; MSIE 6.0; Windows NT 5.1; SV1; .NET CLR 1.1.4322)"
                let! resp = req.AsyncGetResponse()
                use stream = (resp :?> HttpWebResponse).GetResponseStream()
                use reader = new StreamReader(stream)
                let! html = reader.AsyncReadToEnd()
                let doc = new HtmlDocument()
                doc.LoadHtml html
                let stats = doc.DocumentNode.SelectSingleNode(@"//div[@id = 'resultStats']").InnerText
                let numStr = Regex.Match(stats, @"[0-9,]+").Value.Replace(",", "")
                let idf = logWebSizeEstimate - Math.Log(float numStr, 2.)
                dict.AddOrUpdate(word, idf, (fun _ _ -> 0.)) |> ignore
            }
    }
    |> Async.Parallel
    |> Async.RunSynchronously |> ignore
    dict

let TF_IDF (tf : Dictionary<_, _>) (idf : ConcurrentDictionary<_, _>) =
    let uniqueTokens = tf.Keys.ToArray()
    dict [ for word in uniqueTokens ->  word, float tf.[word] * idf.[word] ]

let analyze (text : string) =
    let tokens = text.Split([|' '; '\n'; '\t'; '.'|], StringSplitOptions.RemoveEmptyEntries)
    let tf = makeTFMap tokens
    TF_IDF tf (IDF <| tf.Keys.ToArray())

let printTop text num =
    let results = analyze text
    let hot10 =
        Array.sortBy (fun (kvp : KeyValuePair<_, _>) -> kvp.Value) (results.ToArray())
        |> Array.rev |> Seq.take num
    for kvp in hot10 do
        printfn "%s has TF-IDF of %f" kvp.Key kvp.Value
Пример использования:
let article = 
    "Акции протеста против антиисламского фильма распространяются по всему миру: демонстрации прошли в Ливии,
     Йемене, Египте, Ираке, Тунисе и других странах. Власти Афганистана и Пакистана закрыли доступ к YouTube,
     дабы попытаться не допустить просмотра скандального фильма. Участники акций протеста считают, что
     размещенный в интернете неким 52-летним американцем из Калифорнии любительский фильм, оскорбляет пророка
     Мухаммеда. При штурме американского посольства в Ливии во время акции протеста 
     во вторник погиб американский посол."
printTop article 10
(Текст взят из этой статьи BBC)
Результат:
протеста has TF-IDF of 30.083060
антиисламского has TF-IDF of 18.183107
штурме has TF-IDF of 15.472137
американцем has TF-IDF of 14.996109
Йемене, has TF-IDF of 14.777763
оскорбляет has TF-IDF of 14.299300
скандального has TF-IDF of 14.253497
Тунисе has TF-IDF of 14.058740
размещенный has TF-IDF of 13.757642
неким has TF-IDF of 13.116506
Real: 00:00:09.695, CPU: 00:00:01.248, GC gen0: 41, gen1: 16, gen2: 1

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

Отправить комментарий