我打算测试朴素的贝叶斯分类。其中一部分是建立训练数据的直方图。问题是,我使用大量的培训数据,几年前就已经有了haskell-cafe邮件列表,文件夹中有超过20k的文件。
使用python创建直方图需要一两分钟,而使用haskell需要8分多钟。我正在使用Data.Map(insertWith'),枚举器和文本。我还能做些什么来加速该计划?
Haskell中:
import qualified Data.Text as T
import qualified Data.Text.IO as TI
import System.Directory
import Control.Applicative
import Control.Monad (filterM, foldM)
import System.FilePath.Posix ((</>))
import qualified Data.Map as M
import Data.Map (Map)
import Data.List (foldl')
import Control.Exception.Base (bracket)
import System.IO (Handle, openFile, hClose, hSetEncoding, IOMode(ReadMode), latin1)
import qualified Data.Enumerator as E
import Data.Enumerator (($$), (>==>), (<==<), (==<<), (>>==), ($=), (=$))
import qualified Data.Enumerator.List as EL
import qualified Data.Enumerator.Text as ET
withFile' :: (Handle -> IO c) -> FilePath -> IO c
withFile' f fp = do
bracket
(do
h ← openFile fp ReadMode
hSetEncoding h latin1
return h)
hClose
(f)
buildClassHistogram c = do
files ← filterM doesFileExist =<< map (c </> ) <$> getDirectoryContents c
foldM fileHistogram M.empty files
fileHistogram m file = withFile' (λh → E.run_ $ enumHist h) file
where
enumHist h = ET.enumHandle h $$ EL.fold (λm' l → foldl' (λm'' w → M.insertWith' (const (+1)) w 1 m'') m' $ T.words l) m
的Python:
for filename in listdir(root):
filepath = root + "/" + filename
# print(filepath)
fp = open(filepath, "r", encoding="latin-1")
for word in fp.read().split():
if word in histogram:
histogram[word] = histogram[word]+1
else:
histogram[word] = 1
修改:添加了导入
答案 0 :(得分:8)
您可以尝试使用哈希表包中的命令式哈希映射:http://hackage.haskell.org/package/hashtables 我记得与Data.Map相比,我曾经获得了适度的加速。我不希望看到任何壮观的事。
<强>更新强>
我简化了你的python代码,所以我可以在一个大文件(1亿行)上测试它:
import sys
histogram={}
for word in sys.stdin.readlines():
if word in histogram:
histogram[word] = histogram[word]+1
else:
histogram[word] = 1
print histogram.get("the")
需要6.06秒
使用哈希表进行Haskell翻译:
{-# LANGUAGE OverloadedStrings #-}
import qualified Data.ByteString.Char8 as T
import qualified Data.HashTable.IO as HT
main = do
ls <- T.lines `fmap` T.getContents
h <- HT.new :: IO (HT.BasicHashTable T.ByteString Int)
flip mapM_ ls $ \w -> do
r <- HT.lookup h w
case r of
Nothing -> HT.insert h w (1::Int)
Just c -> HT.insert h w (c+1)
HT.lookup h "the" >>= print
使用较大的分配区域运行:histogram +RTS -A500M
使用2.4%GC需要9.3秒。仍然比Python慢很多但不是太糟糕。
根据GHC user guide,您可以在编译时更改RTS选项:
GHC允许您在编译时更改程序的默认RTS选项 时间,使用-with-rtsopts标志(第4.12.6节,“影响的选项 链接”)。常见的用途是为您的程序提供默认值 堆和/或堆栈大小大于默认值。例如, 设置-H128m -K64m,链接-with-rtsopts =“ - H128m -K64m”。
答案 1 :(得分:7)
您的Haskell和Python实现正在使用具有不同复杂性的地图。 Python字典是哈希映射,因此每个操作(成员资格测试,查找和插入)的预期时间为O(1)。 Haskell版本使用Data.Map,它是一个平衡的二进制搜索树,因此相同的操作需要O(lg n)时间。如果你改变你的Haskell版本以使用不同的地图实现,比如哈希表或某种特里,它应该会更快。但是,我对实现这些数据结构的不同模块不太熟悉,以说哪个是最好的。我从the Data category on Hackage开始,寻找你喜欢的。您也可以查找允许STArray等破坏性更新的地图。
答案 2 :(得分:6)
我们需要更多信息:
两个程序处理输入中的单词需要多长时间,没有用于维护计数的数据结构?
有多少不同的单词,所以我们可以判断平衡树的额外log N
成本是否需要考虑?
GHC的分析师说什么?特别是,分配花了多少时间? Haskell版本可能花费大部分时间来分配很快就过时的树节点。
更新:我错过了小写的“文字”可能意味着Data.Text
。您可能正在比较apply和oranges。 Python的Latin1编码每个字符使用一个字节。虽然它试图提高效率,但Data.Text
必须允许超过256个字符的可能性。如果您切换到String
或更好,Data.ByteString
?
根据这些指标所说的,这里有几件事要尝试:
如果分析输入是瓶颈,请尝试从Data.ByteString
而不是Text
开始所有I / O和分析。
如果数据结构是瓶颈,Bentley和Sedgewick的ternary search trees纯粹是功能性的,但与哈希表竞争性地执行。 Hackage上有一个TernaryTrees
包。