0

私はHaskellをプログラミングすることなく長い時間を過ごしました。比較的進んだプロジェクトで取り組むことに決めました。私はthis guideに従うことによってニューラルネットワークをゼロから作り直そうとしています。私は、重みとバイアスのネットワークを構築するような単純な問題への彼の最も難解なアプローチのいくつかのまわりで私の頭に傷しましたが、それはこれに来るとき:なぜ空の関数合成がHaskellで動作するのですか?

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

私は彼が何をするか理解していません。より具体的には、ここで関数の構成に2つの.を使用する理由がわかりません。彼は(relu <$>) .)を使用しています。この.に続けて括弧を付けても意味がありません。私はそれが関数の構成を表していることを理解しています。この場合、関数zLayerは、タイプ([Float], [[Float]])のニューロンのレイヤーとタイプ[Float]の前のレイヤーの出力を取り込み、タイプの新しい出力を生成します[Float]。私は彼がの結果にrelu <$>関数を適用していることを理解しています。これは意味があります。つまり、脳のレイヤーにzLayerを適用してrelu <$>を適用し、最後にそれを次のレイヤーにinputとして渡して、脳(何もレイヤーのリストではない)を折りたたみたいとします。

一見空の構図が私を悩ますものです。

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

(。私が代わりに整流器(ReLU)のシグモイド関数を使用しています、とcomputeLayerはzLayerのちょうど私の実装である)右:私は上記の何が、私には、このように実装すべきですか?私は(たぶん)、foldl'の関数として、この提供がありやっている:私は(もちろんの前に、オープン括弧)だけ.).computeLayer間を追加すると

(sigmoid <$> (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 
    |           ^^^^^^^^^^^^ 

なぜ機能のこの一見空の組成物には動作しますか?それは場合に役立ちます

これは、これまでに全体のコードです:

@Bergiノートとして
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 'は素敵なトリックですが、それはあまり意味がありません。\ x y - > f(g x y) 'です。後者ははるかに理解できる。そう言われて、私は '(f。)を見始めました。 g 'はそれほど頻繁に使用され、いつかは慣用的になるかもしれない。私はまだ、それを避けるだろう。 Pointfreeコードは、結果がエレガントでクリアな場合にのみ使用する必要があります。 – chi

答えて

6

は、表現((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) 

、その後に簡素化:明らかにISN

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

:対照的に、あなたがその場所に使用していた表現(sigmoid <$>)と等価である、ということ

\f z -> sigmoid <$> f z :: (a -> [Float]) -> a -> [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 

typechecksと同じ結果をプログラムに与えます。

1日の終わりに、あなたの直感はほとんど大丈夫でした。折りたたまれた関数がsigmoidcomputeLayer関数の構成であることを望みましたが、computeLayerが1つではなく2つの引数をとるということは、単純な構成が機能しないことを意味します。あなたの娯楽のため

、次はあまりにも動作します:

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

これは非常に包括的な答えです。ありがとうございました、問題は今はっきりしています。 –

関連する問題