@@ -1,234 +1,95 @@
1{-# LANGUAGE TemplateHaskell #-}
1{-# LANGUAGE TemplateHaskell #-}
2{-# LANGUAGE DataKinds #-}
2{-# LANGUAGE DataKinds #-}
3{-# LANGUAGE GADTs #-}
3{-# LANGUAGE GADTs #-}
4+{-# LANGUAGE ScopedTypeVariables #-}
5+{-# LANGUAGE KindSignatures #-}
6+{-# LANGUAGE ConstraintKinds #-}
7+{-# LANGUAGE TypeOperators #-}
4{-# OPTIONS_GHC -fno-warn-missing-signatures #-}
9{-# OPTIONS_GHC -fno-warn-missing-signatures #-}
5module Test.Grenade.Layers.Convolution where
10module Test.Grenade.Layers.Convolution where
13+import Data.Constraint
15+import Data.Singletons ()
17+import GHC.TypeLits.Witnesses
7import Grenade.Core.Shape
19import Grenade.Core.Shape
8import Grenade.Core.Network
20import Grenade.Core.Network
9import Grenade.Layers.Convolution
21import Grenade.Layers.Convolution
10−import Grenade.Layers.Internal.Convolution
12−import Numeric.LinearAlgebra hiding (uniformSample, konst, (===))
13import qualified Numeric.LinearAlgebra.Static as HStatic
23import qualified Numeric.LinearAlgebra.Static as HStatic
15−import Test.QuickCheck hiding ((><))
25+import Disorder.Jack
17−prop_im2col_no_stride = once $
27+import Test.Jack.Hmatrix
18− let input = (3><4)
28+
19− [ 1.0, 2.0, 3.0, 4.0
29+data OpaqueConvolution :: * where
20− , 5.0, 6.0, 7.0, 8.0
30+ OpaqueConvolution :: Convolution channels filters kernelRows kernelColumns strideRows strideColumns -> OpaqueConvolution
21− , 9.0, 10.0, 11.0, 12.0 ]
31+
22− expected = (6><4)
32+instance Show OpaqueConvolution where
23− [ 1.0, 2.0, 5.0, 6.0
33+ show (OpaqueConvolution n) = show n
24− , 2.0, 3.0, 6.0, 7.0
34+
25− , 3.0, 4.0, 7.0, 8.0
35+genOpaqueOpaqueConvolution :: Jack OpaqueConvolution
26− , 5.0, 6.0, 9.0, 10.0
36+genOpaqueOpaqueConvolution = do
27− , 6.0, 7.0, 10.0, 11.0
37+ Just channels <- someNatVal <$> choose (1, 10)
28− , 7.0, 8.0, 11.0, 12.0 ]
38+ Just filters <- someNatVal <$> choose (1, 10)
29− out = im2col 2 2 1 1 input
39+ Just kernel_h <- someNatVal <$> choose (2, 20)
30− in expected === out
40+ Just kernel_w <- someNatVal <$> choose (2, 20)
31−
41+ Just stride_h <- someNatVal <$> choose (1, 10)
32−prop_im2col_stride = once $
42+ Just stride_w <- someNatVal <$> choose (1, 10)
33− let input = (3><4)
43+ case (channels, filters, kernel_h, kernel_w, stride_h, stride_w) of
34− [ 1.0, 2.0, 3.0, 4.0
44+ ( SomeNat (pch :: Proxy ch), SomeNat (_ :: Proxy fl),
35− , 5.0, 6.0, 7.0, 8.0
45+ SomeNat (pkr :: Proxy kr), SomeNat (pkc :: Proxy kc),
36− , 9.0, 10.0, 11.0, 12.0 ]
46+ SomeNat (_ :: Proxy sr), SomeNat (_ :: Proxy sc)) ->
37− expected = (4><4)
47+ let p1 = natDict pkr
38− [ 1.0, 2.0, 5.0, 6.0
48+ p2 = natDict pkc
39− , 3.0, 4.0, 7.0, 8.0
49+ p3 = natDict pch
40− , 5.0, 6.0, 9.0, 10.0
50+ in case p1 %* p2 %* p3 of
41− , 7.0, 8.0, 11.0, 12.0 ]
51+ Dict -> OpaqueConvolution <$> (Convolution <$> uniformSample <*> uniformSample :: Jack (Convolution ch fl kr kc sr sc))
42− out = im2col 2 2 1 2 input
52+
43− in expected === out
53+prop_conv_net_witness =
44−
54+ gamble genOpaqueOpaqueConvolution $ \onet ->
45−prop_im2col_other = once $
55+ (case onet of
46− let input = (3><4)
56+ (OpaqueConvolution ((Convolution _ _) :: Convolution channels filters kernelRows kernelCols strideRows strideCols)) -> True
47− [ 1.0, 2.0, 3.0, 4.0
57+ ) :: Bool
48− , 5.0, 6.0, 7.0, 8.0
58+
49− , 9.0, 10.0, 11.0, 12.0 ]
59+prop_conv_net =
50− expected = (2><6)
60+ gamble genOpaqueOpaqueConvolution $ \onet ->
51− [ 1.0, 2.0, 5.0, 6.0 , 9.0, 10.0
61+ (case onet of
52− , 3.0, 4.0, 7.0, 8.0 , 11.0 ,12.0 ]
62+ (OpaqueConvolution (convLayer@(Convolution _ _) :: Convolution channels filters kernelRows kernelCols strideRows strideCols)) ->
53− out = im2col 3 2 1 2 input
63+ let ok stride kernel = [extent | extent <- [(kernel + 1) .. 30 ], (extent - kernel) `mod` stride == 0]
54− in expected === out
64+ ch = fromIntegral $ natVal (Proxy :: Proxy channels)
55−
65+ kr = fromIntegral $ natVal (Proxy :: Proxy kernelRows)
56−-- If there's no overlap (stride is the same size as the kernel)
66+ kc = fromIntegral $ natVal (Proxy :: Proxy kernelCols)
57−-- then col2im . im2col should be symmetric.
67+ sr = fromIntegral $ natVal (Proxy :: Proxy strideRows)
58−prop_im2col_sym_on_same_stride = once $
68+ sc = fromIntegral $ natVal (Proxy :: Proxy strideCols)
59− let input = (3><4)
69+
60− [ 1.0, 2.0, 3.0, 4.0
70+ in gamble (elements (ok sr kr)) $ \er ->
61− , 5.0, 6.0, 7.0, 8.0
71+ gamble (elements (ok sc kc)) $ \ec ->
62− , 9.0, 10.0, 11.0, 12.0 ]
72+ let i = fromIntegral (er * ec * ch)
63− out = col2im 3 2 3 2 3 4 . im2col 3 2 3 2 $ input
73+ rr = ((er - kr) `div` sr) + 1
64− in input === out
74+ rc = ((ec - kc) `div` sc) + 1
65−
75+ er' = someNatVal er
66−-- If there is an overlap, then the gradient passed back should be
76+ ec' = someNatVal ec
67−-- the sum of the gradients across the filters.
77+ rr' = someNatVal rr
68−prop_im2col_col2im_additive = once $
78+ rc' = someNatVal rc
69− let input = (3><4)
79+ in gamble (vectorOf i sizedRealFrac) $ \(input :: [Double]) ->
70− [ 1.0, 1.0, 1.0, 1.0
80+ case (er', ec', rr', rc') of
71− , 1.0, 1.0, 1.0, 1.0
81+ (Just (SomeNat (pinr :: Proxy inRows)), Just (SomeNat (_ :: Proxy inCols)), Just (SomeNat (pour :: Proxy outRows)), Just (SomeNat (_ :: Proxy outCols))) ->
72− , 1.0, 1.0, 1.0, 1.0 ]
82+ let p1 = natDict pinr
73− expected = (3><4)
83+ p2 = natDict pour
74− [ 1.0, 2.0, 2.0, 1.0
84+ in case ( p1 %* natDict (Proxy :: Proxy channels)
75− , 2.0, 4.0, 4.0, 2.0
85+ , p2 %* natDict (Proxy :: Proxy filters)
76− , 1.0, 2.0, 2.0, 1.0 ]
86+ -- Fake it till you make it.
77− out = col2im 2 2 1 1 3 4 . im2col 2 2 1 1 $ input
87+ , (unsafeCoerce (Dict :: Dict ()) :: Dict (((outRows - 1) * strideRows) ~ (inRows - kernelRows)))
78− in expected === out
88+ , (unsafeCoerce (Dict :: Dict ()) :: Dict (((outCols - 1) * strideCols) ~ (inCols - kernelCols)))) of
79−
89+ (Dict, Dict, Dict, Dict) -> let x :: S' ('D3 outRows outCols filters) = runForwards convLayer ((S3D' (HStatic.matrix input)) :: S' ('D3 inRows inCols channels))
80−prop_simple_conv_forwards = once $
90+ in x `seq` True
81− -- Create a convolution kernel with 4 filters.
91+ _ -> False
82− -- [ 1, 0 [ 0, 1 [ 0, 1 [ 0, 0
92+ ) :: Property
83− -- , 0,-1 ] ,-1, 0 ] , 1, 0 ] ,-1,-1 ]
84− let myKernel = HStatic.matrix
87− , 0.0, -1.0, 1.0, -1.0
88− ,-1.0, 0.0, 0.0, -1.0 ] :: HStatic.L 4 4
89− zeroKernel = HStatic.matrix
93− , 0.0, 0.0, 0.0, 0.0 ] :: HStatic.L 4 4
95− expectedGradient = HStatic.matrix
99− , 4.0, 0.0, 0.0, 6.0 ] :: HStatic.L 4 4
101− convLayer = Convolution myKernel zeroKernel :: Convolution 1 4 2 2 1 1
103− input = S2D' (HStatic.matrix
105− , 3.0, 4.0, 6.0] :: HStatic.L 2 3)
107− expect = HStatic.matrix
111− , -7.0 , -10.0 ] :: HStatic.L 4 2
112− out = runForwards convLayer input :: S' ('D3 1 2 4)
114− grad = S3D' ( HStatic.matrix
118− , 0 , 1 ] :: HStatic.L 4 2 ) :: S' ('D3 1 2 4)
120− expectBack = (HStatic.matrix
122− , 0.0, -2.0,-1.0] :: HStatic.L 2 3)
123− (nc, inX) = runBackwards convLayer input grad
125− in case (out, inX, nc) of
126− (S3D' out' , S2D' inX', Convolution' backGrad)
127− -> (HStatic.extract expect === HStatic.extract out')
128− .&&. (HStatic.extract expectBack === HStatic.extract inX')
129− .&&. (HStatic.extract expectedGradient === HStatic.extract backGrad)
131−prop_vid2col_no_stride = once $
133− [ 1.0, 2.0, 3.0, 4.0
134− , 5.0, 6.0, 7.0, 8.0
135− , 9.0, 10.0, 11.0, 12.0
137− , 21.0, 22.0, 23.0, 24.0
138− , 25.0, 26.0, 27.0, 28.0
139− , 29.0, 30.0, 31.0, 32.0 ]
141− [ 1.0, 2.0, 5.0, 6.0 , 21.0, 22.0, 25.0, 26.0
142− , 2.0, 3.0, 6.0, 7.0 , 22.0, 23.0, 26.0, 27.0
143− , 3.0, 4.0, 7.0, 8.0 , 23.0, 24.0, 27.0, 28.0
144− , 5.0, 6.0, 9.0, 10.0 , 25.0, 26.0, 29.0, 30.0
145− , 6.0, 7.0, 10.0, 11.0 , 26.0, 27.0, 30.0, 31.0
146− , 7.0, 8.0, 11.0, 12.0 , 27.0, 28.0, 31.0, 32.0 ]
147− out_c = vid2col 2 2 1 1 3 4 input
148− in expected === out_c
150−prop_vid2col_stride = once $
152− [ 1.0, 2.0, 3.0, 4.0
153− , 5.0, 6.0, 7.0, 8.0
154− , 9.0, 10.0, 11.0, 12.0
156− , 21.0, 22.0, 23.0, 24.0
157− , 25.0, 26.0, 27.0, 28.0
158− , 29.0, 30.0, 31.0, 32.0 ]
160− [ 1.0, 2.0, 5.0, 6.0 , 21.0, 22.0, 25.0, 26.0
161− , 3.0, 4.0, 7.0, 8.0 , 23.0, 24.0, 27.0, 28.0
162− , 5.0, 6.0, 9.0, 10.0 , 25.0, 26.0, 29.0, 30.0
163− , 7.0, 8.0, 11.0, 12.0 , 27.0, 28.0, 31.0, 32.0 ]
164− out_c = vid2col 2 2 1 2 3 4 input
165− in expected === out_c
167−prop_vid2col_invert = once $
169− [ 1.0, 2.0, 3.0, 4.0
170− , 5.0, 6.0, 7.0, 8.0
171− , 9.0, 10.0, 11.0, 12.0
173− , 21.0, 22.0, 23.0, 24.0
174− , 25.0, 26.0, 27.0, 28.0
175− , 29.0, 30.0, 31.0, 32.0 ]
176− out = col2vid 3 2 3 2 3 4 . vid2col 3 2 3 2 3 4 $ input
180−-- This test show that 2D convs act the same
181−-- 3D convs with one layer
182−prop_single_conv_forwards = once $
183− -- Create a convolution kernel with 4 filters.
184− -- [ 1, 0 [ 0, 1 [ 0, 1 [ 0, 0
185− -- , 0,-1 ] ,-1, 0 ] , 1, 0 ] ,-1,-1 ]
186− let myKernel = (HStatic.matrix
187− [ 1.0, 0.0, 0.0, 0.0
188− , 0.0, 1.0, 1.0, 0.0
189− , 0.0, -1.0, 1.0, -1.0
190− ,-1.0, 0.0, 0.0, -1.0 ] :: HStatic.L 4 4)
191− zeroKernel = (HStatic.matrix
192− [ 0.0, 0.0, 0.0, 0.0
193− , 0.0, 0.0, 0.0, 0.0
194− , 0.0, 0.0, 0.0, 0.0
195− , 0.0, 0.0, 0.0, 0.0 ] :: HStatic.L 4 4)
197− expectedGradient = (HStatic.matrix
198− [ 1.0, 0.0, 0.0, 2.0
199− , 2.0, 0.0, 0.0, 5.0
200− , 3.0, 0.0, 0.0, 4.0
201− , 4.0, 0.0, 0.0, 6.0 ] :: HStatic.L 4 4)
203− convLayer = Convolution myKernel zeroKernel :: Convolution 1 4 2 2 1 1
205− input = S3D' ( HStatic.matrix
207− , 3.0, 4.0, 6.0] :: HStatic.L 2 3 ) :: S' ('D3 2 3 1)
209− expect = HStatic.matrix
213− , -7.0 , -10.0 ] :: HStatic.L 4 2
214− out = runForwards convLayer input :: S' ('D3 1 2 4)
216− grad = S3D' (HStatic.matrix
220− , 0 , 1 ] :: HStatic.L 4 2 ) :: S' ('D3 1 2 4)
222− expectBack = (HStatic.matrix
224− , 0.0, -2.0,-1.0] :: HStatic.L 2 3)
225− (nc, inX) = runBackwards convLayer input grad
227− in case (out, inX, nc) of
228− (S3D' out' , S3D' inX', Convolution' backGrad)
229− -> (HStatic.extract expect === HStatic.extract out')
230− .&&. (HStatic.extract expectBack === HStatic.extract inX')
231− .&&. (HStatic.extract expectedGradient === HStatic.extract backGrad)
234tests :: IO Bool
95tests :: IO Bool