0

我花了很長的時間,無需編程哈斯克爾,並決定採取一個相對先進的項目重新進入它。我試圖通過遵循this guide從頭開始編程一個神經網絡。我劃傷了我的身邊了他的一些最深奧的方法,如創建重量和偏見的網絡的簡單問題的頭,但是當涉及到這一點:爲什麼空函數組合在Haskell中工作?

feed :: [Float] -> [([Float], [[Float]])] -> [Float] 
feed input brain = foldl' (((relu <$>) .) . zLayer) input brain 

我不明白他做什麼。更具體地說,我不明白爲什麼在這裏使用兩個.的函數組合。他使用(relu <$>) .)。這個.跟着括號對我來說沒有意義。我明白它代表功能組合物,並且在這種情況下,函數zLayer發生在神經元的層,其是([Float], [[Float]])類型和先前層,這是[Float]類型的輸出,併產生一個新的輸出,也類型的[Float]。我明白他的應用relu <$>功能的zLayer的結果,這是有道理的。也就是說,要通過大腦的層上應用zLayer,然後對這個結果將relu <$>摺疊大腦(這是什麼,但圖層的列表),並最終通過,作爲input到下一層。

看似空洞的成分是什麼錯誤我。

feed :: [Float] -> [([Float], [[Float]])] -> [Float] 
feed inp brain = foldl' (((sigmoid <$>) . computeLayer) inp brain 

(我使用的雙曲線函數,而不是整流器(RELU)和computeLayer只是我實現zLayer的)右:我上面描述應該是什麼,對我來說,是這樣實現的?我在做什麼有(據說)提供,作爲功能foldl',這樣的:

(sigmoid <$> (computeLayer)) 

當我(當然之前和開放的括號中)僅增加.).computeLayer之間,它的工作原理。沒有他們,這是錯誤的:

net.hs:42:42: error: 
    • Couldn't match type ‘[Float]’ with ‘Float’ 
     Expected type: [Float] -> ([Float], [[Float]]) -> Float 
     Actual type: [Float] -> ([Float], [[Float]]) -> [Float] 
    • In the second argument of ‘(.)’, namely ‘computeLayer’ 
     In the first argument of ‘foldl'’, namely 
     ‘((sigmoid <$>) . computeLayer)’ 
     In the expression: foldl' ((sigmoid <$>) . computeLayer) inp brain 
    | 
42 | feed inp brain = foldl' ((sigmoid <$>) . computeLayer) inp brain 
    |           ^^^^^^^^^^^^ 

爲什麼這個看似空的函數組合工作?

這是整個代碼到目前爲止,如果它可以幫助:

import System.Random 
import Control.Monad 
import Data.Functor 

foldl' f z []  = z 
foldl' f z (x:xs) = let z' = z `f` x 
        in seq z' $ foldl' f z' xs 

sigmoid :: Float -> Float 
sigmoid x = 1/(1 + (exp 1) ** (-x)) 

-- Given a list, gives out a list of lists of length *each element of the list* 
makeBiases :: [Int] -> Float -> [[Float]] 
makeBiases x b = flip replicate b <$> x 

-- Given a list, gives out, for each element X in the list, a list of length x + 1, of 
-- x elements in any normal distribution 
makeWeights :: [Int] -> Float -> [[[Float]]] 
makeWeights [email protected](_:xs) el = zipWith (\m n -> replicate n (replicate m el)) xl xs 

-- Make initial biases and weights to give a list of tuples that corresponds to biases 
-- and weights associated with each node in each layer 
makeBrain :: [Int] -> Float -> Float -> [([Float], [[Float]])] 
makeBrain (x:xs) b el = zip (makeBiases xs b) (makeWeights (x:xs) el) 

-- Given output of a layer, apply weights and sum for all nodes in a layer. For each list 
-- of weights (each node has multiple inputs), there will be one output 
sumWeightsL l wvs = sum . zipWith (*) l <$> wvs 

-- Given output of a layer, apply weights to get tentative output of each node. Then 
-- sum biases of each node to its output 
computeLayer :: [Float] -> ([Float], [[Float]]) -> [Float] 
computeLayer l (bs, wvs) = zipWith (+) bs (sumWeightsL l wvs) 

feed :: [Float] -> [([Float], [[Float]])] -> [Float] 
feed inp brain = foldl' ((sigmoid <$>) . computeLayer) inp brain 

main = do 
    putStrLn "3 inputs, a hidden layer of 4 neurons, and 2 output neurons:" 
    print $ feed [0.1, 0.2, 0.3] (makeBrain [3,4,2] 0 0.22) 
+2

這是一個[段](https://wiki.haskell.org/Section_of_an_infix_operator) – Bergi

+1

「他用'(RELU <$>))'。這個「。」後面跟着一個括號對我來說沒有意義。「如果你問這個表達式的類型(回想起'relu'將被解釋爲一個類型化的洞,如果它沒有綁定)那麼你會得到一個解析錯誤。這應該是一個暗示,您沒有正確地將該程序的子字符串標識爲子表達式。如果這樣做沒有意義,可以嘗試用前綴函數替換所有中綴運算符(一旦你完成了這個操作,同時保留了語義,你可能已經理解了中綴運算符的實際意思)。 – user2407038

+5

在我看來,這是一個毫無意義風格的典範。寫作(f。)。 g'是一個很好的技巧,但它並不比那個有意義的'\ xy - > f(g x y)'短得多。後者更容易理解。這就是說,我已經開始看到'(f。)。 g'用得這麼頻繁,甚至有一天甚至會變得習慣用語。不過,我仍然會避免它。只有當結果是優雅和清晰時,IMO才能使用Pointfree代碼。 – chi

回答

6

由於@Bergi筆記,表達((relu <$>) .)不是「空函數組合」,而是一種叫做「節」。 (實際上,在這種情況下,它是嵌套在另一部分內的部分。)

即使您忘記了它被調用和/或沒有意識到它應用於功能複合算(.),只是提醒你...

在Haskell,對於任何二進制運算符(如(+)),你可以寫一個向左或向右「節」:

(1+) -- short for \x -> 1+x 
(+1) -- short for \x -> x+1 

這樣的東西像map (2*) mylist可以用來加倍列表中的每個元素,而不必編寫map (\x -> 2*x) mylist

它適用於函數組合(.)和FMAP操作者(<$>)以同樣的方式,所以:

((sigmoid <$>) .) 

是短期的:

\f -> (sigmoid <$>) . f 

其是短爲:

\f -> (\xs -> sigmoid <$> xs) . f 

你可以擴展到:

\f z -> (\xs -> sigmoid <$> xs) (f z) 

,然後簡化爲:

\f z -> sigmoid <$> f z :: (a -> [Float]) -> a -> [Float] 

需要注意的是,與此相反,如果您想在它的地方使用表達式(sigmoid <$>)等同於:

\xs -> sigmoid <$> xs :: [Float] -> [Float] 

這顯然ISN同樣的。

無論如何,這一切都意味着,摺疊功能:

(((sigmoid <$>) .) . computeLayer) 

可以ETA擴展和簡化如下:

\acc x -> (((sigmoid <$>) .) . computeLayer) acc x 
\acc x -> ((sigmoid <$>) .) (computeLayer acc) x 
\acc x -> (\f z -> sigmode <$> f z) (computeLayer acc) x 
\acc x -> sigmoid <$> (computeLayer acc) x 
\acc x -> sigmoid <$> computeLayer acc x 

,你可以快速驗證修改後的定義:

feed inp brain = foldl' (\acc x -> sigmoid <$> computeLayer acc x) inp brain 

typechecks並在您的程序中給出相同的結果。

在一天結束時,你的直覺大部分都沒問題。您希望摺疊功能是sigmoidcomputeLayer功能的組合,但computeLayer只有兩個參數而不是一個參數,這意味着簡單組合不起作用。

爲了您的娛樂,下面的工作太:

feed inp brain = foldl' (((.).(.)) (sigmoid <$>) computeLayer) inp brain 
+0

這是一個非常全面的答案。非常感謝你,現在這個問題很明顯。 –

相關問題