________fear_of_underscores
Показаны сообщения с ярлыком f#. Показать все сообщения
Показаны сообщения с ярлыком f#. Показать все сообщения

пятница, 19 октября 2012 г.

Фича F#, о которой я не знал

Иногда бывает нужно воспользоваться в pipe-lineing'e каррированной функцией, но на этом этапе вычислений мы уже работаем с кортежем. Как быть? В F# есть четыре прекрасных оператора для этого. Вот два из них. Два других угадайте сами :)

А вот примерчик:
List.partition (fun x -> x % 2 = 0) [1..10] ||> (@)
//val it : int list = [2; 4; 6; 8; 10; 1; 3; 5; 7; 9]
P.S. Читаю только что купленную Programming F# 3.0 — масса наслаждения и няшности.

вторник, 25 сентября 2012 г.

F#un tee pipe

В Linux есть прекрасная команда tee, которая берёт вывод какой-то команды, записывает его в файл, и в то же время выводит на консоль:

А ещё в Linux shell есть pipe-lining, очень похожий на pipe-lining из F#.

Эти два обстоятельства и одна задачка, возникшая на работе, натолкнули меня на мысль, что в    F# можно добавить аналог tee. Вот, например, такой код:
open System

let rnd = new Random()
[| for _ in 0..100 -> rnd.Next(100) |] .|> "array.txt" |> Array.sum |> printfn "%i"
создаст массив из случайных целых чисел, запишет его в файл "array.txt", посчитает сумму элементов массива и напечатает её на консоль. Осталось только реализовать этот магический оператор (.|>)!

К счастью, в F# подобные задачки решаются тривиально:
open System.IO

let inline ( .|> ) data fileName =
    File.WriteAllLines(fileName, Seq.map (sprintf "%A") data)
    data

//val inline ( .|> ) : 'a -> string -> 'a when 'a :> seq<'b>
Обратите внимание: сия магия работает только с коллекциями (для меня это самый частый use case). На самом деле, можно использовать WriteAllText и sprintf "%A" data соответственно, но тогда слишком длинные коллекции будут обрубаться.

четверг, 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

среда, 20 июня 2012 г.

MS Visual Studio Template for Data Analysis

Уже довольно давно я пользуюсь няшным шаблоном для VS, который помогает в пару кликов мышки создать проект F#, в котором легко можно делать матричные операции с помощью Math.Net Numerics, использовать HtmlAgilityPack и рисовать графики с помощью FSharpChart:


Я сделал этот шаблон, когда понял, что если ещё один раз буду вручную добавлять все нужные библиотеки — повешусь. Тем не менее, сам процесс создания шаблона не обошёлся без пары gotchas, так что я решил записать его; сейчас же перепишу его сюда и приложу сам шаблон, на благо общественности.

В шаблоне используются библиотеки, добытые с помощью NuGet'a и скрипт FSharpChart. Все права на всё принадлежат тем, кому они принадлежат ^_^.

АлгоритмЪ:
  1. Создать обычный проект F#, включить в него все нужные библиотеки и файлы, загрузить все нужные NuGet-пакеты, написать весь boilerplate код. В моём случае это выглядело так:
    #if INTERACTIVE 
    //Math.Net Numerics
    #r @"..\packages\MathNet.Numerics.2.1.2\lib\Net40\MathNet.Numerics.dll"
    #r @"..\packages\MathNet.Numerics.FSharp.2.1.2\lib\Net40\MathNet.Numerics.FSharp.dll"
    //PowerPack
    #r @"..\packages\FSPowerPack.Community.2.1.1.1\Lib\Net40\FSharp.PowerPack.dll"
    #r @"..\packages\FSPowerPack.Community.2.1.1.1\Lib\Net40\FSharp.PowerPack.Linq.dll"
    #r @"..\packages\FSPowerPack.Community.2.1.1.1\Lib\Net40\FSharp.PowerPack.Metadata.dll"
    #r @"..\packages\FSPowerPack.Community.2.1.1.1\Lib\Net40\FSharp.PowerPack.Parallel.Seq.dll"
    //FSharpChart
    #r @"..\packages\MSDN.FSharpChart.dll.0.60\lib\MSDN.FSharpChart.dll"
    #load "FSharpChart.fsx"
    //HtmlAgility
    #r @"..\packages\HtmlAgilityPack.1.4.3\lib\HtmlAgilityPack.dll"
    #r @"System.Xml"
    #r @"System.Xml.Linq"
    #endif
    
    open System
    open System.Collections.Generic
    open System.Linq
    open System.Text
    open System.IO
    open System.Drawing
    open System.Windows.Forms
    open System.Windows.Forms.DataVisualization.Charting
    open System.Xml
    open System.Xml.Linq
    
    open MathNet.Numerics
    open MathNet.Numerics.FSharp
    open MathNet.Numerics.LinearAlgebra.Double
    open MathNet.Numerics.LinearAlgebra.IO
    open MathNet.Numerics.Distributions
    open MSDN.FSharp.Charting  //FSharpChart
    open HtmlAgilityPack       //HtmlAgility
    open Microsoft.FSharp.Math //PowerPack
    
  2. File > Export Template...
  3. Получится .zip файл, в корневую папку которого надо вставить все .nupkg файлы:
  4. В файле MyTemplate.vstemplate надо поменять метаданные в разделе TemplateData :
    <TemplateData>
        <Name>Data Analysis App Extended</Name>
        <Description>Data Analysis Application with installed Math.Net Numerics,
             FSharpChart, HtmlAgility and F# PowerPack libraries.</Description>
        <ProjectType>FSharp</ProjectType>
        ...
    </TemplateData>
    
  5. В разделе WizardData нужно добавить информацию о всех используемых NuGet-пакетах:
    <WizardData>
        <packages repository="template">
          <package id="zlib.net" version="1.0.3.0" />
          <package id="MathNet.Numerics" version="2.1.2" />
          <package id="MathNet.Numerics.FSharp" version="2.1.2" />
          <package id="MSDN.FSharpChart.dll" version="0.60" />
          <package id="HtmlAgilityPack" version="1.4.3" />
          <package id="FSPowerPack.Community" version="2.1.1.1" />
        </packages>
      </WizardData>
    
  6. Наконец, в самом файле проекта .fsproj, нужно убедиться, что используются относительные пути для NuGet-пакетов:
    <PropertyGroup>
        ...
        <ReferencePath>..\packages\</ReferencePath>
    </PropertyGroup>
    ...
    <ItemGroup>
        <Compile Include="Program.fs" />
        <None Include="packages.config" />
        <None Include="FSharpChart.fsx" />
    </ItemGroup>
    <ItemGroup>
        <Reference Include="FSharp.PowerPack">
          <HintPath>..\packages\FSPowerPack.Community.2.1.1.1\Lib\Net40\FSharp.PowerPack.dll</HintPath>
          <Private>True</Private>
        </Reference>
        <Reference Include="FSharp.PowerPack.Linq">
          <HintPath>..\packages\FSPowerPack.Community.2.1.1.1\Lib\Net40\FSharp.PowerPack.Linq.dll</HintPath>
          <Private>True</Private>
        </Reference>
        <Reference Include="FSharp.PowerPack.Metadata">
          <HintPath>..\packages\FSPowerPack.Community.2.1.1.1\Lib\Net40\FSharp.PowerPack.Metadata.dll</HintPath>
          <Private>True</Private>
        </Reference>
        <Reference Include="FSharp.PowerPack.Parallel.Seq">
          <HintPath>..\packages\FSPowerPack.Community.2.1.1.1\Lib\Net40\FSharp.PowerPack.Parallel.Seq.dll</HintPath>
          <Private>True</Private>
        </Reference>
        <Reference Include="HtmlAgilityPack">
          <HintPath>..\packages\HtmlAgilityPack.1.4.3\lib\HtmlAgilityPack.dll</HintPath>
          <Private>True</Private>
        </Reference>
        <Reference Include="mscorlib" />
        <Reference Include="FSharp.Core" />
        <Reference Include="System" />
        ...
        <Reference Include="System.Xml" />
        <Reference Include="System.Xml.Linq" />
        <Reference Include="MathNet.Numerics">
          <HintPath>..\packages\MathNet.Numerics.2.1.2\lib\Net40\MathNet.Numerics.dll</HintPath>
          <Private>True</Private>
        </Reference>
        <Reference Include="MathNet.Numerics.FSharp">
          <HintPath>..\packages\MathNet.Numerics.FSharp.2.1.2\lib\Net40\MathNet.Numerics.FSharp.dll</HintPath>
          <Private>True</Private>
        </Reference>
        <Reference Include="MSDN.FSharpChart">
          <HintPath>..\packages\MSDN.FSharpChart.dll.0.60\lib\MSDN.FSharpChart.dll</HintPath>
          <Private>True</Private>
        </Reference>
        <Reference Include="zlib.net">
          <HintPath>..\packages\zlib.net.1.0.3.0\lib\zlib.net.dll</HintPath>
          <Private>True</Private>
        </Reference>
    </ItemGroup>
    
    Значение тега <Private>true — значит, что все .dll будут скопированы в папку создаваемого с помощью шаблона проекта.
  7. Чтобы добавить исправленные файлы обратно в .zip-архив, нужно использовать Windows Explorer — просто выделите и перетащите их на .zip файл. Я понятия не имею, почему не работает добавление с помощью WinRar'а, но я смог добиться успеха в сём предприятии только так.
  8. Осталось только перетащить исправленный .zip архив с шаблоном в папку C:\Users\...\Documents\Visual Studio 2010\Templates\ProjectTemplates, и новый шаблон появится в диалоге создания нового проекта.
Ну и, как и было обещано, сам шаблон (или на github'e).
После создания проекта запустите компиляцию, чтобы VS нашла все библиотеки. Alt+Enter поможет вам проверить работу шаблона:
FSharpChart.Point
    [|
        let gaussian = new Normal()        
        for _ in 1..100 -> gaussian.Sample(), gaussian.Sample() |]