@@ -0,0 +1,251 @@
1+{-# LANGUAGE TemplateHaskell #-}
2+{-# LANGUAGE DataKinds #-}
3+{-# LANGUAGE GADTs #-}
4+{-# OPTIONS_GHC -fno-warn-missing-signatures #-}
5+module Test.Grenade.Layers.Convolution where
6+
7+import Control.Monad.Identity
8+
9+import Grenade.Core.Shape
10+import Grenade.Core.Vector as Grenade
11+import Grenade.Core.Network
12+import Grenade.Layers.Convolution
13+
14+import Numeric.LinearAlgebra hiding (uniformSample, konst, (===))
15+import qualified Numeric.LinearAlgebra.Static as HStatic
16+
17+import Test.QuickCheck hiding ((><))
18+
19+prop_im2col_no_stride = once $
20+ let input = (3><4)
21+ [ 1.0, 2.0, 3.0, 4.0
22+ , 5.0, 6.0, 7.0, 8.0
23+ , 9.0, 10.0, 11.0, 12.0 ]
24+ expected = (6><4)
25+ [ 1.0, 2.0, 5.0, 6.0
26+ , 2.0, 3.0, 6.0, 7.0
27+ , 3.0, 4.0, 7.0, 8.0
28+ , 5.0, 6.0, 9.0, 10.0
29+ , 6.0, 7.0, 10.0, 11.0
30+ , 7.0, 8.0, 11.0, 12.0 ]
31+ out = im2col 2 2 1 1 input
32+ in expected === out
33+
34+prop_im2col_stride = once $
35+ let input = (3><4)
36+ [ 1.0, 2.0, 3.0, 4.0
37+ , 5.0, 6.0, 7.0, 8.0
38+ , 9.0, 10.0, 11.0, 12.0 ]
39+ expected = (4><4)
40+ [ 1.0, 2.0, 5.0, 6.0
41+ , 3.0, 4.0, 7.0, 8.0
42+ , 5.0, 6.0, 9.0, 10.0
43+ , 7.0, 8.0, 11.0, 12.0 ]
44+ out = im2col 2 2 1 2 input
45+ in expected === out
46+
47+prop_im2col_other = once $
48+ let input = (3><4)
49+ [ 1.0, 2.0, 3.0, 4.0
50+ , 5.0, 6.0, 7.0, 8.0
51+ , 9.0, 10.0, 11.0, 12.0 ]
52+ expected = (2><6)
53+ [ 1.0, 2.0, 5.0, 6.0 , 9.0, 10.0
54+ , 3.0, 4.0, 7.0, 8.0 , 11.0 ,12.0 ]
55+ out = im2col 3 2 1 2 input
56+ in expected === out
57+
58+-- If there's no overlap (stride is the same size as the kernel)
59+-- then col2im . im2col should be symmetric.
60+prop_im2col_sym_on_same_stride = once $
61+ let input = (3><4)
62+ [ 1.0, 2.0, 3.0, 4.0
63+ , 5.0, 6.0, 7.0, 8.0
64+ , 9.0, 10.0, 11.0, 12.0 ]
65+ out = col2im 3 2 3 2 3 4 . im2col 3 2 3 2 $ input
66+ in input === out
67+
68+-- If there is an overlap, then the gradient passed back should be
69+-- the sum of the gradients across the filters.
70+prop_im2col_col2im_additive = once $
71+ let input = (3><4)
72+ [ 1.0, 1.0, 1.0, 1.0
73+ , 1.0, 1.0, 1.0, 1.0
74+ , 1.0, 1.0, 1.0, 1.0 ]
75+ expected = (3><4)
76+ [ 1.0, 2.0, 2.0, 1.0
77+ , 2.0, 4.0, 4.0, 2.0
78+ , 1.0, 2.0, 2.0, 1.0 ]
79+ out = col2im 2 2 1 1 3 4 . im2col 2 2 1 1 $ input
80+ in expected === out
81+
82+prop_simple_conv_forwards = once $
83+ -- Create a convolution kernel with 4 filters.
84+ -- [ 1, 0 [ 0, 1 [ 0, 1 [ 0, 0
85+ -- , 0,-1 ] ,-1, 0 ] , 1, 0 ] ,-1,-1 ]
86+ let myKernel = (HStatic.matrix
87+ [ 1.0, 0.0, 0.0, 0.0
88+ , 0.0, 1.0, 1.0, 0.0
89+ , 0.0, -1.0, 1.0, -1.0
90+ ,-1.0, 0.0, 0.0, -1.0 ] :: HStatic.L 4 4)
91+ zeroKernel = (HStatic.matrix
92+ [ 0.0, 0.0, 0.0, 0.0
93+ , 0.0, 0.0, 0.0, 0.0
94+ , 0.0, 0.0, 0.0, 0.0
95+ , 0.0, 0.0, 0.0, 0.0 ] :: HStatic.L 4 4)
96+ --expectedKernel = (HStatic.matrix
97+ -- [ 0.0, 0.0, 0.0, -2.0
98+ -- ,-2.0, 1.0, 1.0, -5.0
99+ -- ,-3.0, -1.0, 1.0, -5.0
100+ -- ,-5.0, 0.0, 0.0, -7.0 ] :: HStatic.L 4 4)
101+
102+ convLayer = Convolution myKernel zeroKernel :: Convolution 1 4 2 2 1 1
103+
104+ input = S2D' (HStatic.matrix
105+ [ 1.0, 2.0, 5.0
106+ , 3.0, 4.0, 6.0] :: HStatic.L 2 3)
107+
108+ expect = ([(HStatic.matrix
109+ [ -3.0 , -4.0 ] :: HStatic.L 1 2)
110+ ,(HStatic.matrix
111+ [ -1.0 , 1.0 ] :: HStatic.L 1 2)
112+ ,(HStatic.matrix
113+ [ 5.0 , 9.0 ] :: HStatic.L 1 2)
114+ ,(HStatic.matrix
115+ [ -7.0 , -10.0 ] :: HStatic.L 1 2)]) :: [HStatic.L 1 2]
116+ out = runIdentity $ runForwards convLayer input :: S' ('D3 1 2 4)
117+
118+ grad = S3D' ( mkVector
119+ [(HStatic.matrix
120+ [ 1 , 0 ] :: HStatic.L 1 2)
121+ ,(HStatic.matrix
122+ [ 0 , 0 ] :: HStatic.L 1 2)
123+ ,(HStatic.matrix
124+ [ 0 , 0 ] :: HStatic.L 1 2)
125+ ,(HStatic.matrix
126+ [ 0 , 1 ] :: HStatic.L 1 2)] ) :: S' ('D3 1 2 4)
127+
128+ expectBack = (HStatic.matrix
129+ [ 1.0, 0.0, 0.0
130+ , 0.0, -2.0,-1.0] :: HStatic.L 2 3)
131+ (nc, inX) = runIdentity $ runBackards 1 convLayer input grad :: ( Convolution 1 4 2 2 1 1 , S' ('D2 2 3))
132+
133+ in case (out, inX, nc) of
134+ (S3D' out' , S2D' inX', Convolution _ _)
135+ -> ((HStatic.extract <$> expect) === (HStatic.extract <$> vecToList out'))
136+ .&&. ((HStatic.extract expectBack) === (HStatic.extract inX'))
137+ -- Temporarily disabled, as l2 adjustment puts in off 5%
138+ -- .&&. HStatic.extract expectedKernel === HStatic.extract kernel'
139+
140+
141+prop_vid2col_no_stride = once $
142+ let input = [(3><4)
143+ [ 1.0, 2.0, 3.0, 4.0
144+ , 5.0, 6.0, 7.0, 8.0
145+ , 9.0, 10.0, 11.0, 12.0 ]
146+ , (3><4)
147+ [ 21.0, 22.0, 23.0, 24.0
148+ , 25.0, 26.0, 27.0, 28.0
149+ , 29.0, 30.0, 31.0, 32.0 ] ]
150+ expected = (6><8)
151+ [ 1.0, 2.0, 5.0, 6.0 , 21.0, 22.0, 25.0, 26.0
152+ , 2.0, 3.0, 6.0, 7.0 , 22.0, 23.0, 26.0, 27.0
153+ , 3.0, 4.0, 7.0, 8.0 , 23.0, 24.0, 27.0, 28.0
154+ , 5.0, 6.0, 9.0, 10.0 , 25.0, 26.0, 29.0, 30.0
155+ , 6.0, 7.0, 10.0, 11.0 , 26.0, 27.0, 30.0, 31.0
156+ , 7.0, 8.0, 11.0, 12.0 , 27.0, 28.0, 31.0, 32.0 ]
157+ out = vid2col 2 2 1 1 3 4 input
158+ in expected === out
159+
160+prop_vid2col_stride = once $
161+ let input = [(3><4)
162+ [ 1.0, 2.0, 3.0, 4.0
163+ , 5.0, 6.0, 7.0, 8.0
164+ , 9.0, 10.0, 11.0, 12.0 ]
165+ , (3><4)
166+ [ 21.0, 22.0, 23.0, 24.0
167+ , 25.0, 26.0, 27.0, 28.0
168+ , 29.0, 30.0, 31.0, 32.0 ] ]
169+ expected = (4><8)
170+ [ 1.0, 2.0, 5.0, 6.0 , 21.0, 22.0, 25.0, 26.0
171+ , 3.0, 4.0, 7.0, 8.0 , 23.0, 24.0, 27.0, 28.0
172+ , 5.0, 6.0, 9.0, 10.0 , 25.0, 26.0, 29.0, 30.0
173+ , 7.0, 8.0, 11.0, 12.0 , 27.0, 28.0, 31.0, 32.0 ]
174+ out = vid2col 2 2 1 2 3 4 input
175+ in expected === out
176+
177+
178+prop_vid2col_invert = once $
179+ let input = [(3><4)
180+ [ 1.0, 2.0, 3.0, 4.0
181+ , 5.0, 6.0, 7.0, 8.0
182+ , 9.0, 10.0, 11.0, 12.0 ]
183+ , (3><4)
184+ [ 21.0, 22.0, 23.0, 24.0
185+ , 25.0, 26.0, 27.0, 28.0
186+ , 29.0, 30.0, 31.0, 32.0 ] ]
187+ out = col2vid 3 2 3 2 3 4 . vid2col 3 2 3 2 3 4 $ input
188+ in input === out
189+
190+-- This test show that 2D convs act the same
191+-- 3D convs with one layer
192+prop_single_conv_forwards = once $
193+ -- Create a convolution kernel with 4 filters.
194+ -- [ 1, 0 [ 0, 1 [ 0, 1 [ 0, 0
195+ -- , 0,-1 ] ,-1, 0 ] , 1, 0 ] ,-1,-1 ]
196+ let myKernel = (HStatic.matrix
197+ [ 1.0, 0.0, 0.0, 0.0
198+ , 0.0, 1.0, 1.0, 0.0
199+ , 0.0, -1.0, 1.0, -1.0
200+ ,-1.0, 0.0, 0.0, -1.0 ] :: HStatic.L 4 4)
201+ zeroKernel = (HStatic.matrix
202+ [ 0.0, 0.0, 0.0, 0.0
203+ , 0.0, 0.0, 0.0, 0.0
204+ , 0.0, 0.0, 0.0, 0.0
205+ , 0.0, 0.0, 0.0, 0.0 ] :: HStatic.L 4 4)
206+ --expectedKernel = (HStatic.matrix
207+ -- [ 0.0, 0.0, 0.0, -2.0
208+ -- ,-2.0, 1.0, 1.0, -5.0
209+ -- ,-3.0, -1.0, 1.0, -5.0
210+ -- ,-5.0, 0.0, 0.0, -7.0 ] :: HStatic.L 4 4)
211+
212+ convLayer = Convolution myKernel zeroKernel :: Convolution 1 4 2 2 1 1
213+
214+ input = S3D' ( mkVector [HStatic.matrix
215+ [ 1.0, 2.0, 5.0
216+ , 3.0, 4.0, 6.0] :: HStatic.L 2 3] ) :: S' ('D3 2 3 1)
217+
218+ expect = ([(HStatic.matrix
219+ [ -3.0 , -4.0 ] :: HStatic.L 1 2)
220+ ,(HStatic.matrix
221+ [ -1.0 , 1.0 ] :: HStatic.L 1 2)
222+ ,(HStatic.matrix
223+ [ 5.0 , 9.0 ] :: HStatic.L 1 2)
224+ ,(HStatic.matrix
225+ [ -7.0 , -10.0 ] :: HStatic.L 1 2)]) :: [HStatic.L 1 2]
226+ out = runIdentity $ runForwards convLayer input :: S' ('D3 1 2 4)
227+
228+ grad = S3D' ( mkVector
229+ [(HStatic.matrix
230+ [ 1 , 0 ] :: HStatic.L 1 2)
231+ ,(HStatic.matrix
232+ [ 0 , 0 ] :: HStatic.L 1 2)
233+ ,(HStatic.matrix
234+ [ 0 , 0 ] :: HStatic.L 1 2)
235+ ,(HStatic.matrix
236+ [ 0 , 1 ] :: HStatic.L 1 2)] ) :: S' ('D3 1 2 4)
237+
238+ expectBack = (HStatic.matrix
239+ [ 1.0, 0.0, 0.0
240+ , 0.0, -2.0,-1.0] :: HStatic.L 2 3)
241+ (nc, inX) = runIdentity $ runBackards 1 convLayer input grad :: ( Convolution 1 4 2 2 1 1 , S' ('D3 2 3 1))
242+
243+ in case (out, inX, nc) of
244+ (S3D' out' , S3D' inX', Convolution _ _)
245+ -> ((HStatic.extract <$> expect) === (HStatic.extract <$> vecToList out'))
246+ .&&. ([HStatic.extract expectBack] === (HStatic.extract <$> vecToList inX'))
247+ -- .&&. HStatic.extract expectedKernel === HStatic.extract kernel'
248+
249+return []
250+tests :: IO Bool
251+tests = $quickCheckAll