为什么空函数组合在Haskell中有效?

And*_*icy 1 haskell functional-programming purely-functional neural-network function-composition

我花了很长时间没有编程Haskell,并决定通过采用一个相对先进的项目回到它.我正在尝试按照本指南从头开始编程神经网络.我已经摸索了一些他最神秘的方法来解决诸如创建权重和偏见网络之类的简单问题,但是当涉及到这个时:

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

我不明白他做了什么.更具体地说,我不明白为什么.在这里使用函数组合中的两个.他使用(relu <$>) . ).这.后面的括号对我来说没有意义.我理解它代表函数组合,在这种情况下,函数zLayer接受一层神经元,它是类型([Float], [[Float]])和前一层的输出,它是类型[Float],并产生一个新的输出,也是类型[Float].我理解他正在将relu <$>函数应用于结果zLayer,这是有道理的.也就是说,你想要通过在大脑层上应用来折叠大脑(这只是一个层列表)zLayer,然后应用relu <$>其结果,最后将其作为input下一层传递.

看似空洞的构图是我的错.对我来说,上面描述的内容应该像这样实现:

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

(我使用的是sigmoid函数而不是整流器(ReLU),而computeLayer只是我对zLayer的实现.)对吗?我在那里做的是(据说)提供,作为一个功能foldl',这:

(sigmoid <$> (computeLayer))
Run Code Online (Sandbox Code Playgroud)

当我.)在我.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
   |                                          ^^^^^^^^^^^^
Run Code Online (Sandbox Code Playgroud)

为什么这个看似空洞的功能组合起作用?

这是到目前为止的整个代码,如果它有帮助:

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 xl@(_: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)
Run Code Online (Sandbox Code Playgroud)

K. *_*uhr 6

正如@Bergi所说,表达式((relu <$>) . )不是"空函数组合",而是称为"部分".(事实上​​,在这种情况下,它是嵌套在另一部分内的部分.)

你以前无疑已经看过这个,即使你已经忘记它被称为和/或没有意识到它应用于函数组合运算符(.),但只是提醒你......

在Haskell中,对于任何二元运算符(如(+)),您可以编写左或右"部分":

(1+)  -- short for   \x -> 1+x
(+1)  -- short for   \x -> x+1
Run Code Online (Sandbox Code Playgroud)

所以类似的东西map (2*) mylist可以用来加倍列表的每个元素而不必写map (\x -> 2*x) mylist.

它的功能组合(.)和fmap运算符的工作方式相同(<$>),因此:

((sigmoid <$>) . )
Run Code Online (Sandbox Code Playgroud)

是短的:

\f -> (sigmoid <$>) . f
Run Code Online (Sandbox Code Playgroud)

这是短的:

\f -> (\xs -> sigmoid <$> xs) . f
Run Code Online (Sandbox Code Playgroud)

你可以扩展到:

\f z -> (\xs -> sigmoid <$> xs) (f z)
Run Code Online (Sandbox Code Playgroud)

然后简化为:

\f z -> sigmoid <$> f z    :: (a -> [Float]) -> a -> [Float]
Run Code Online (Sandbox Code Playgroud)

请注意,相反,(sigmoid <$>)您想要在其位置使用的表达式等效于:

\xs -> sigmoid <$> xs    :: [Float] -> [Float]
Run Code Online (Sandbox Code Playgroud)

这显然是不一样的.

无论如何,这一切都意味着折叠功能:

(((sigmoid <$>) .) . computeLayer)
Run Code Online (Sandbox Code Playgroud)

可以如下扩展和简化:

\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
Run Code Online (Sandbox Code Playgroud)

并且您可以快速验证修改后的定义:

feed inp brain = foldl' (\acc x -> sigmoid <$> computeLayer acc x) inp brain
Run Code Online (Sandbox Code Playgroud)

typechecks并在您的程序中给出相同的结果.

在一天结束时,你的直觉基本上没问题.您希望折叠函数是函数sigmoidcomputeLayer函数的组合,但是computeLayer需要两个参数而不是一个参数的事实意味着简单的组合不起作用.

为了您的娱乐,以下也适用:

feed inp brain = foldl' (((.).(.)) (sigmoid <$>) computeLayer) inp brain
Run Code Online (Sandbox Code Playgroud)