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

时间:2017-09-02 21:44:56

标签: haskell functional-programming neural-network function-composition purely-functional

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

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

(我使用的是sigmoid函数而不是整流器(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 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)

1 个答案:

答案 0 :(得分:6)

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

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

在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]

显然不一样。

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

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

可以如下扩展和简化:

\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