LINUX.ORG.RU

[haskell][Тормоза]

 ,


0

2

Принципиально не понимаю в чем тут дело. Короче. Вот модуль создания N-грамм из текста и генерации по ним произволного текста


{-# LANGUAGE FlexibleInstances, TypeSynonymInstances, BangPatterns #-}
module NGram where

import System.Random
import Control.Monad.Trans.Maybe
import Control.Monad.Trans.Class
import qualified Data.Map as M
import Control.Arrow
import Data.List (inits, tails, foldl')
import System.Environment

-- На самом деле не случайный а в зависимости от поданного числа. сдесь первый
-- аргумент это число по которому будет выбираться элемент, а в списке кортежей
-- первый элемент кортежа есть относительное значение вероятности для выброки
getRandomElement :: (Num a, Ord a) => a -> [(a, b)] -> Maybe b
getRandomElement _ [] = Nothing
getRandomElement elem xs = let el = min elem $ probSum xs
                           in Just $ snd $ head $ filter (\((l, g), ret) -> el >= l && el <= g) lowHighList
    where
    -- probSum :: (Ord a, Num a) => [(a, b)] -> a
    probSum = sum . map fst
    -- lowHighList :: (Random a, Num a, Ord a) => [((a, a), b)]
    lowHighList = map (((\x -> (probSum x) - (last $ map fst x)) &&& probSum) &&& (last . map snd)) $
                   tail $ inits xs

-- Указываем длинну нграммы и список элементов (строку) получаем словарь где
-- ключем является строка а значением ее относительная вероятность появления
makeNgram :: (Ord a, Num b) => Int -> [a] -> M.Map [a] b
makeNgram n str = foldl' foldf M.empty $ nlists n str
  where
    foldf m s = M.insertWith (+) s 1 m
    nlists :: Int -> [a] -> [[a]]
    nlists n str = map (take n)
                   $ take (length str + 1 - n)
                   $ tails str

-- Эта функция использует только одно IO действие - взятие случайного числа
generateSeq :: (Random b, Ord b, Num b) => Int -> M.Map [a] b -> MaybeT IO [a]
generateSeq amount ng = do
  let mlist = map (snd &&& fst) $ M.toList ng
  let fsum = sum $ map fst mlist
  gen <- lift newStdGen          -- вот тут
  let rnds = take amount $ randomRs (0, fsum) gen
  MaybeT . return $ (mapM (\x -> getRandomElement x mlist) rnds) >>= return . concat

А вот программа которая пользуя этот модуль просто выводит N-грамму на стандартный вывод

module Main where

import Control.Monad.Trans.Maybe
import Control.Monad.Trans.Class (lift)
import Data.Map as M
import System.Environment
import System.IO
import NGram

main = do
  a <- getArgs
  if length a /= 2
    then usage
    else do let len = read $ a !! 0
            cont <- readFile $ a !! 1
            let result = show ((makeNgram len cont) :: M.Map String Double)
            putStrLn result
              

usage :: IO ()
usage = putStrLn "Need 2 arguments :: length filename"

Вот так эта программа отрабатывает

18:39 razor@localhost /home/razor/projs/haskell/ngrams % du -h data.txt          
12K     data.txt
18:40 razor@localhost /home/razor/projs/haskell/ngrams % time ./nggen 4 data.txt > out
./nggen 4 data.txt > out  0,06s user 0,01s system 80% cpu 0,082 total

первым парметром мы подали длинну N-граммы а вторым указали файло с данными для обработки. Заметим что время работы всего 0.01сек. Далее делаем программу которая вместо голой N-граммы выводит текст сгенерированный на ее еснове

module Main where

import System.Environment
import System.IO
import Control.Monad.Trans.Maybe (runMaybeT)
import Control.Monad.Trans.Class (lift)
import Control.DeepSeq
import Data.Map as M
import NGram
  

main = runMaybeT $ do
  args <- lift $ getArgs
  if length args /= 3
    then lift usage
    else do let len = read $ args !! 0
            let amount = read $ args !! 1
            hfile <- lift $ openFile (args !! 2) ReadMode
            cont <- lift $ hGetContents hfile
            cont `deepseq` lift $ hClose hfile
            out <- generateSeq amount $ (makeNgram len cont :: M.Map String Int)
            lift $ putStrLn out

usage :: IO ()
usage = putStrLn "Need 3 arguments length, amount and filename"

И вот так она работает

18:43 razor@localhost /home/razor/projs/haskell/ngrams % time ./ngfilter 4 2 data.txt > out
./ngfilter 4 2 data.txt > out  4,05s user 0,01s system 99% cpu 4,067 total
18:45 razor@localhost /home/razor/projs/haskell/ngrams % time ./ngfilter 4 3 data.txt > out
./ngfilter 4 3 data.txt > out  16,85s user 0,05s system 99% cpu 16,930 total
18:45 razor@localhost /home/razor/projs/haskell/ngrams % time ./ngfilter 4 10 data.txt > out
./ngfilter 4 10 data.txt > out  40,86s user 0,11s system 99% cpu 41,044 total

От количества выведенных Нграмм (второй аргумент) время работы зависит прямо скажем странно. То есть либо Нграмма вычисляется не один раз либо я не знаю даже что. deepseq почему то не дает эффекта строгости. Прозреваю что тут дело в IO но где именно ?

Нграммы перепер отсюда http://habrahabr.ru/post/135127/

Простейший профайлинг показывает, что ./ngfilter 4 3 data.txt проводит почти всё время в функции getRandomElement.

Miguel ★★★★★
()
Ответ на: комментарий от Miguel

Упс, это был глюк. Не на порядок, раза в два.

Miguel ★★★★★
()
lowHighList = map (((\x -> (probSum x) - (last $ map fst x)) &&& probSum) &&& (last . map snd)) $ tail $ inits xs

Это просто праздник быдлокода какой-то. Честное слово, не будь ЛОРа, я бы и не знал, что на Хаскеле бывает быдлокод.

lowHighList = zip (zip sums $ tail sums) (map snd xs) where sums = scanl (+) 0 (map fst xs)
Вот это - быстрее даже не на порядок, а на два порядка.

Miguel ★★★★★
()
Ответ на: комментарий от Miguel

Круто, а я inits научился использовать у этого дурачка с хабра, больше не буду его читать, он там в статье дальше предлагал генерить код хаскеля чтобы тот случайный текст генерировал на основе нграмм, ну то есть текст > нграмма > код на хаскеле > компиляция > запускаем > сгенерированный текст

На счет мемоизации а можно по подробнее ? Я думал что хаскель и так умный и все мои выкрутасы с inits замемоизирует и при повторной подаче данных не станет перевычислять их, ведь чистые же фунции, повторные вызовы возвращают тоже самое и компилятор это знает.

А как вообще можно привести этот алгоритм к аналогу на сишечке или питоне ? Ну то есть чтобы список из ((Нижняя_граница, Верхняя_граница), Элемент) генерировался один раз, а элементы выбирались циклического путем выбора очередного случайного числа и поиска соответствующего этому числу элемента массива ?

s9gf4ult ★★
() автор топика
Ответ на: комментарий от s9gf4ult

Я думал что хаскель и так умный и все мои выкрутасы с inits замемоизирует и при повторной подаче данных не станет перевычислять их, ведь чистые же фунции, повторные вызовы возвращают тоже самое и компилятор это знает.

Автоматически мемоизировать всё на свете — значит напрашиваться на неприятности. Делать это никто не будет.

Ну то есть чтобы список из ((Нижняя_граница, Верхняя_граница), Элемент) генерировался один раз

Генерировать его один раз, не?

А как вообще можно привести этот алгоритм к аналогу на сишечке или питоне ?

Не понял, что требуется. Переписать это всё на питоне? Так а какие проблемы-то?

Miguel ★★★★★
()
Ответ на: комментарий от Miguel
module NGram where

import System.Random
import Control.Monad.Trans.Maybe
import Control.Monad.Trans.Class
import qualified Data.Map as M
import Control.Arrow
import Data.List (inits, tails, foldl')
import System.Environment

getRandomElement :: (Num a, Ord a) => a -> [((a, a), b)] -> Maybe b
getRandomElement _ [] = Nothing
getRandomElement elem xs = let el = max 0 $ min elem $ probSum xs
                           in Just $ snd $ head $ filter (\((l, g), ret) -> el >= l && el <= g) xs
    where
    probSum = snd . fst . last 

lowHighList :: Num a => [(a, b)] -> [((a, a), b)]
lowHighList xs = zip (zip sums $ tail sums) (map snd xs)
  where
    sums = scanl (+) 0 (map fst xs)

makeNgram :: (Ord a, Num b) => Int -> [a] -> M.Map [a] b
makeNgram n str = foldl' foldf M.empty $ nlists n str
  where
    foldf m s = M.insertWith (+) s 1 m
    nlists :: Int -> [a] -> [[a]]
    nlists n str = map (take n)
                   $ take (length str + 1 - n)
                   $ tails str

generateSeq :: (Random b, Ord b, Num b) => Int -> M.Map [a] b -> MaybeT IO [a]
generateSeq amount ng = do
  let mlist = lowHighList $ map (snd &&& fst) $ M.toList ng
  let fsum = (snd . fst . last) mlist
  gen <- lift newStdGen
  let rnds = take amount $ randomRs (0, fsum) gen
  MaybeT . return $ (mapM (\x -> getRandomElement x mlist) rnds) >>= return . concat

Спасибо, кэп. теперь работает примемлемо и от количества генерируемых кусков зависит линейно. Вот интересно, есть ли у GHC прагмы для указания кэширования функции или типа того ?

s9gf4ult ★★
() автор топика
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.