@@ -1,234 +1,95 @@
11{-# LANGUAGE TemplateHaskell #-}
22{-# LANGUAGE DataKinds #-}
33{-# LANGUAGE GADTs #-}
4+{-# LANGUAGE ScopedTypeVariables #-}
5+{-# LANGUAGE KindSignatures #-}
6+{-# LANGUAGE ConstraintKinds #-}
7+{-# LANGUAGE TypeOperators #-}
8+
49{-# OPTIONS_GHC -fno-warn-missing-signatures #-}
510module Test.Grenade.Layers.Convolution where
611
12+import Unsafe.Coerce
13+import Data.Constraint
14+import Data.Proxy
15+import Data.Singletons ()
16+import GHC.TypeLits
17+import GHC.TypeLits.Witnesses
18+
719import Grenade.Core.Shape
820import Grenade.Core.Network
921import Grenade.Layers.Convolution
10−import Grenade.Layers.Internal.Convolution
1122
12−import Numeric.LinearAlgebra hiding (uniformSample, konst, (===))
1323import qualified Numeric.LinearAlgebra.Static as HStatic
1424
15−import Test.QuickCheck hiding ((><))
16−
17−prop_im2col_no_stride = once $
18− let input = (3><4)
19− [ 1.0, 2.0, 3.0, 4.0
20− , 5.0, 6.0, 7.0, 8.0
21− , 9.0, 10.0, 11.0, 12.0 ]
22− expected = (6><4)
23− [ 1.0, 2.0, 5.0, 6.0
24− , 2.0, 3.0, 6.0, 7.0
25− , 3.0, 4.0, 7.0, 8.0
26− , 5.0, 6.0, 9.0, 10.0
27− , 6.0, 7.0, 10.0, 11.0
28− , 7.0, 8.0, 11.0, 12.0 ]
29− out = im2col 2 2 1 1 input
30− in expected === out
31−
32−prop_im2col_stride = once $
33− let input = (3><4)
34− [ 1.0, 2.0, 3.0, 4.0
35− , 5.0, 6.0, 7.0, 8.0
36− , 9.0, 10.0, 11.0, 12.0 ]
37− expected = (4><4)
38− [ 1.0, 2.0, 5.0, 6.0
39− , 3.0, 4.0, 7.0, 8.0
40− , 5.0, 6.0, 9.0, 10.0
41− , 7.0, 8.0, 11.0, 12.0 ]
42− out = im2col 2 2 1 2 input
43− in expected === out
44−
45−prop_im2col_other = once $
46− let input = (3><4)
47− [ 1.0, 2.0, 3.0, 4.0
48− , 5.0, 6.0, 7.0, 8.0
49− , 9.0, 10.0, 11.0, 12.0 ]
50− expected = (2><6)
51− [ 1.0, 2.0, 5.0, 6.0 , 9.0, 10.0
52− , 3.0, 4.0, 7.0, 8.0 , 11.0 ,12.0 ]
53− out = im2col 3 2 1 2 input
54− in expected === out
55−
56−-- If there's no overlap (stride is the same size as the kernel)
57−-- then col2im . im2col should be symmetric.
58−prop_im2col_sym_on_same_stride = once $
59− let input = (3><4)
60− [ 1.0, 2.0, 3.0, 4.0
61− , 5.0, 6.0, 7.0, 8.0
62− , 9.0, 10.0, 11.0, 12.0 ]
63− out = col2im 3 2 3 2 3 4 . im2col 3 2 3 2 $ input
64− in input === out
65−
66−-- If there is an overlap, then the gradient passed back should be
67−-- the sum of the gradients across the filters.
68−prop_im2col_col2im_additive = once $
69− let input = (3><4)
70− [ 1.0, 1.0, 1.0, 1.0
71− , 1.0, 1.0, 1.0, 1.0
72− , 1.0, 1.0, 1.0, 1.0 ]
73− expected = (3><4)
74− [ 1.0, 2.0, 2.0, 1.0
75− , 2.0, 4.0, 4.0, 2.0
76− , 1.0, 2.0, 2.0, 1.0 ]
77− out = col2im 2 2 1 1 3 4 . im2col 2 2 1 1 $ input
78− in expected === out
79−
80−prop_simple_conv_forwards = once $
81− -- Create a convolution kernel with 4 filters.
82− -- [ 1, 0 [ 0, 1 [ 0, 1 [ 0, 0
83− -- , 0,-1 ] ,-1, 0 ] , 1, 0 ] ,-1,-1 ]
84− let myKernel = HStatic.matrix
85− [ 1.0, 0.0, 0.0, 0.0
86− , 0.0, 1.0, 1.0, 0.0
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
90− [ 0.0, 0.0, 0.0, 0.0
91− , 0.0, 0.0, 0.0, 0.0
92− , 0.0, 0.0, 0.0, 0.0
93− , 0.0, 0.0, 0.0, 0.0 ] :: HStatic.L 4 4
94−
95− expectedGradient = HStatic.matrix
96− [ 1.0, 0.0, 0.0, 2.0
97− , 2.0, 0.0, 0.0, 5.0
98− , 3.0, 0.0, 0.0, 4.0
99− , 4.0, 0.0, 0.0, 6.0 ] :: HStatic.L 4 4
100−
101− convLayer = Convolution myKernel zeroKernel :: Convolution 1 4 2 2 1 1
102−
103− input = S2D' (HStatic.matrix
104− [ 1.0, 2.0, 5.0
105− , 3.0, 4.0, 6.0] :: HStatic.L 2 3)
106−
107− expect = HStatic.matrix
108− [ -3.0 , -4.0
109− , -1.0 , 1.0
110− , 5.0 , 9.0
111− , -7.0 , -10.0 ] :: HStatic.L 4 2
112− out = runForwards convLayer input :: S' ('D3 1 2 4)
113−
114− grad = S3D' ( HStatic.matrix
115− [ 1 , 0
116− , 0 , 0
117− , 0 , 0
118− , 0 , 1 ] :: HStatic.L 4 2 ) :: S' ('D3 1 2 4)
119−
120− expectBack = (HStatic.matrix
121− [ 1.0, 0.0, 0.0
122− , 0.0, -2.0,-1.0] :: HStatic.L 2 3)
123− (nc, inX) = runBackwards convLayer input grad
124−
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)
130−
131−prop_vid2col_no_stride = once $
132− let input = (6><4)
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
136− -- -- --
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 ]
140− expected = (6><8)
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
149−
150−prop_vid2col_stride = once $
151− let input = (6><4)
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
155− -- -- -- -- --
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 ]
159− expected = (4><8)
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
166−
167−prop_vid2col_invert = once $
168− let input = (6><4)
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
172− -- -- -- -- --
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
177− in input === out
178−
179−
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)
196−
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)
202−
203− convLayer = Convolution myKernel zeroKernel :: Convolution 1 4 2 2 1 1
204−
205− input = S3D' ( HStatic.matrix
206− [ 1.0, 2.0, 5.0
207− , 3.0, 4.0, 6.0] :: HStatic.L 2 3 ) :: S' ('D3 2 3 1)
208−
209− expect = HStatic.matrix
210− [ -3.0 , -4.0
211− , -1.0 , 1.0
212− , 5.0 , 9.0
213− , -7.0 , -10.0 ] :: HStatic.L 4 2
214− out = runForwards convLayer input :: S' ('D3 1 2 4)
215−
216− grad = S3D' (HStatic.matrix
217− [ 1 , 0
218− , 0 , 0
219− , 0 , 0
220− , 0 , 1 ] :: HStatic.L 4 2 ) :: S' ('D3 1 2 4)
221−
222− expectBack = (HStatic.matrix
223− [ 1.0, 0.0, 0.0
224− , 0.0, -2.0,-1.0] :: HStatic.L 2 3)
225− (nc, inX) = runBackwards convLayer input grad
226−
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)
25+import Disorder.Jack
26+
27+import Test.Jack.Hmatrix
28+
29+data OpaqueConvolution :: * where
30+ OpaqueConvolution :: Convolution channels filters kernelRows kernelColumns strideRows strideColumns -> OpaqueConvolution
31+
32+instance Show OpaqueConvolution where
33+ show (OpaqueConvolution n) = show n
34+
35+genOpaqueOpaqueConvolution :: Jack OpaqueConvolution
36+genOpaqueOpaqueConvolution = do
37+ Just channels <- someNatVal <$> choose (1, 10)
38+ Just filters <- someNatVal <$> choose (1, 10)
39+ Just kernel_h <- someNatVal <$> choose (2, 20)
40+ Just kernel_w <- someNatVal <$> choose (2, 20)
41+ Just stride_h <- someNatVal <$> choose (1, 10)
42+ Just stride_w <- someNatVal <$> choose (1, 10)
43+ case (channels, filters, kernel_h, kernel_w, stride_h, stride_w) of
44+ ( SomeNat (pch :: Proxy ch), SomeNat (_ :: Proxy fl),
45+ SomeNat (pkr :: Proxy kr), SomeNat (pkc :: Proxy kc),
46+ SomeNat (_ :: Proxy sr), SomeNat (_ :: Proxy sc)) ->
47+ let p1 = natDict pkr
48+ p2 = natDict pkc
49+ p3 = natDict pch
50+ in case p1 %* p2 %* p3 of
51+ Dict -> OpaqueConvolution <$> (Convolution <$> uniformSample <*> uniformSample :: Jack (Convolution ch fl kr kc sr sc))
52+
53+prop_conv_net_witness =
54+ gamble genOpaqueOpaqueConvolution $ \onet ->
55+ (case onet of
56+ (OpaqueConvolution ((Convolution _ _) :: Convolution channels filters kernelRows kernelCols strideRows strideCols)) -> True
57+ ) :: Bool
58+
59+prop_conv_net =
60+ gamble genOpaqueOpaqueConvolution $ \onet ->
61+ (case onet of
62+ (OpaqueConvolution (convLayer@(Convolution _ _) :: Convolution channels filters kernelRows kernelCols strideRows strideCols)) ->
63+ let ok stride kernel = [extent | extent <- [(kernel + 1) .. 30 ], (extent - kernel) `mod` stride == 0]
64+ ch = fromIntegral $ natVal (Proxy :: Proxy channels)
65+ kr = fromIntegral $ natVal (Proxy :: Proxy kernelRows)
66+ kc = fromIntegral $ natVal (Proxy :: Proxy kernelCols)
67+ sr = fromIntegral $ natVal (Proxy :: Proxy strideRows)
68+ sc = fromIntegral $ natVal (Proxy :: Proxy strideCols)
69+
70+ in gamble (elements (ok sr kr)) $ \er ->
71+ gamble (elements (ok sc kc)) $ \ec ->
72+ let i = fromIntegral (er * ec * ch)
73+ rr = ((er - kr) `div` sr) + 1
74+ rc = ((ec - kc) `div` sc) + 1
75+ er' = someNatVal er
76+ ec' = someNatVal ec
77+ rr' = someNatVal rr
78+ rc' = someNatVal rc
79+ in gamble (vectorOf i sizedRealFrac) $ \(input :: [Double]) ->
80+ case (er', ec', rr', rc') of
81+ (Just (SomeNat (pinr :: Proxy inRows)), Just (SomeNat (_ :: Proxy inCols)), Just (SomeNat (pour :: Proxy outRows)), Just (SomeNat (_ :: Proxy outCols))) ->
82+ let p1 = natDict pinr
83+ p2 = natDict pour
84+ in case ( p1 %* natDict (Proxy :: Proxy channels)
85+ , p2 %* natDict (Proxy :: Proxy filters)
86+ -- Fake it till you make it.
87+ , (unsafeCoerce (Dict :: Dict ()) :: Dict (((outRows - 1) * strideRows) ~ (inRows - kernelRows)))
88+ , (unsafeCoerce (Dict :: Dict ()) :: Dict (((outCols - 1) * strideCols) ~ (inCols - kernelCols)))) of
89+ (Dict, Dict, Dict, Dict) -> let x :: S' ('D3 outRows outCols filters) = runForwards convLayer ((S3D' (HStatic.matrix input)) :: S' ('D3 inRows inCols channels))
90+ in x `seq` True
91+ _ -> False
92+ ) :: Property
23293
23394return []
23495tests :: IO Bool