为什么空函数组合在 Haskell 中有效?
Why does empty function composition work in Haskell?
我花了很长时间没有编程 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)
正如@Bergi 指出的那样,表达式 ((relu <$>) . )
不是 "empty function composition" 而是一种叫做 "section" 的东西。 (实际上,在这种情况下,它是一个嵌套在另一个部分中的部分。)
你以前肯定见过这个,即使你忘记了它叫什么and/or没有意识到它适用于函数组合运算符(.)
,只是提醒你...
在Haskell中,对于任何二元运算符(如(+)
),你可以写左或右"section":
(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)
可以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
类型检查并在您的程序中给出相同的结果。
归根结底,您的直觉基本没问题。您希望折叠函数是 sigmoid
和 computeLayer
函数的组合,但 computeLayer
接受两个参数而不是一个参数这一事实意味着简单组合不起作用。
为了您的娱乐,以下内容也适用:
feed inp brain = foldl' (((.).(.)) (sigmoid <$>) computeLayer) inp brain
我花了很长时间没有编程 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)
正如@Bergi 指出的那样,表达式 ((relu <$>) . )
不是 "empty function composition" 而是一种叫做 "section" 的东西。 (实际上,在这种情况下,它是一个嵌套在另一个部分中的部分。)
你以前肯定见过这个,即使你忘记了它叫什么and/or没有意识到它适用于函数组合运算符(.)
,只是提醒你...
在Haskell中,对于任何二元运算符(如(+)
),你可以写左或右"section":
(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)
可以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
类型检查并在您的程序中给出相同的结果。
归根结底,您的直觉基本没问题。您希望折叠函数是 sigmoid
和 computeLayer
函数的组合,但 computeLayer
接受两个参数而不是一个参数这一事实意味着简单组合不起作用。
为了您的娱乐,以下内容也适用:
feed inp brain = foldl' (((.).(.)) (sigmoid <$>) computeLayer) inp brain