Skip to content

Commit 4c2f50f

Browse files
0.4.3 NN - Autograd - Random - Plot - Faster iteration (#42)
* testing yielding over iterators * tri iterator * iter complete * sorting for tensors * matrix exponential using pade * neural network basics * remove req * move images * full implementation of autograd and better networks * Added plplot bindings * merge PRs * docs * remove dataframes * more activation * default bias * random creation methods using alea
1 parent 694080e commit 4c2f50f

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

66 files changed

+4932
-695
lines changed

README.md

+50-41
Original file line numberDiff line numberDiff line change
@@ -187,61 +187,70 @@ puts a.matmul(a)
187187
# [15, 22]]
188188
```
189189

190-
### DataFrames
190+
### Machine Learning
191191

192-
For more structured data, consider using a `DataFrame`
192+
`Num::Grad` provides a pure-crystal approach to find derivatives of
193+
mathematical functions. Use a `Num::Grad::Variable` with a `Num::Grad::Context`
194+
to easily compute these derivatives.
193195

194196
```crystal
195-
df = DataFrame.from_items(
196-
foo: [1, 2, 3, 4, 5].to_tensor,
197-
bar: [2.73, 3.1, 4.8, 5.1, 3.2],
198-
)
199-
200-
puts df
201-
202-
# foo bar
203-
# 0 1 2.73
204-
# 1 2 3.1
205-
# 2 3 4.8
206-
# 3 4 5.1
207-
# 4 5 3.2
197+
ctx = Num::Grad::Context(Tensor(Float64)).new
198+
199+
x = ctx.variable([3.0])
200+
y = ctx.variable([2.0])
201+
202+
# f(x) = x ** y
203+
f = x ** y
204+
puts f # => [9]
205+
206+
f.backprop
207+
208+
# df/dx = y * x = 6.0
209+
puts x.grad # => [6.0]
208210
```
209211

210-
A `DataFrame` maintains types while still providing convenient
211-
mapping and reduction operations
212+
`Num::NN` contains an extension to `Num::Grad` that provides an easy-to-use
213+
interface to assist in creating neural networks. Designing and creating
214+
a network is simple using Crystal's block syntax.
212215

213216
```crystal
214-
puts df.c[:foo]
217+
ctx = Num::Grad::Context(Tensor(Float64)).new
215218
216-
# 0 1
217-
# 1 2
218-
# 2 3
219-
# 3 4
220-
# 4 5
221-
# Name: foo
222-
# dtype: Int32
219+
x_train = [[0.0, 0.0], [1.0, 0.0], [0.0, 1.0], [1.0, 1.0]].to_tensor
220+
y_train = [[0.0], [1.0], [1.0], [0.0]].to_tensor
223221
224-
puts typeof(df.c[:foo])
222+
x = ctx.variable(x_train)
225223
226-
# Series(Int32, Int32)
224+
net = Num::NN::Network.new(ctx) do
227225
228-
puts df.sum
226+
# A basic network with a single hidden layer using
227+
# a ReLU activation function
228+
linear(2, 3)
229+
relu
230+
linear(3, 1)
229231
230-
# foo 15
231-
# bar 18.93
232-
```
232+
# SGD Optimizer
233+
sgd 0.7
233234
234-
With operations that broadcast across the `DataFrame`
235+
# Sigmoid Cross Entropy to calculate loss
236+
sigmoid_cross_entropy_loss
237+
end
235238
236-
```crystal
237-
puts df.greater(df.mean)
238-
239-
# foo bar
240-
# 0 false false
241-
# 1 false false
242-
# 2 false true
243-
# 3 true true
244-
# 4 true false
239+
500.times do |epoch|
240+
y_pred = net.forward(x)
241+
loss = net.loss(y_pred, y_train)
242+
puts "Epoch: #{epoch} - Loss #{loss}"
243+
loss.backprop
244+
net.optimizer.update
245+
end
246+
247+
# Clip results to make a prediction
248+
puts net.forward(x).value.map { |el| el > 0 ? 1 : 0}
249+
250+
# [[0],
251+
# [1],
252+
# [1],
253+
# [0]]
245254
```
246255

247256
Review the documentation for full implementation details, and if something is missing,
+74
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
## Basic XOR Classifier
2+
3+
The following implements a simple XOR classifier to show how to use
4+
`num.cr`'s `Network` class. Plotting is done via `ishi`.
5+
6+
```crystal
7+
ctx = Num::Grad::Context(Tensor(Float64)).new
8+
9+
bsz = 32
10+
11+
x_train_bool = Tensor.random(0_u8...2_u8, [bsz * 100, 2])
12+
13+
y_bool = x_train_bool[..., ...1] ^ x_train_bool[..., 1...]
14+
15+
x_train = ctx.variable(x_train_bool.as_type(Float64))
16+
y = y_bool.as_type(Float64)
17+
18+
net = Num::NN::Network.new(ctx) do
19+
linear 2, 3
20+
relu
21+
linear 3, 1
22+
sgd 0.7
23+
sigmoid_cross_entropy_loss
24+
end
25+
26+
losses = [] of Float64
27+
28+
50.times do |epoch|
29+
100.times do |batch_id|
30+
offset = batch_id * 32
31+
x = x_train[offset...offset + 32]
32+
target = y[offset...offset + 32]
33+
34+
y_pred = net.forward(x)
35+
36+
loss = net.loss(y_pred, target)
37+
38+
puts "Epoch is: #{epoch}"
39+
puts "Batch id: #{batch_id}"
40+
puts "Loss is: #{loss.value.value}"
41+
losses << loss.value.value
42+
43+
loss.backprop
44+
net.optimizer.update
45+
end
46+
end
47+
```
48+
49+
```
50+
...
51+
Epoch is: 49
52+
Batch id: 95
53+
Loss is: 0.00065050072686102
54+
Epoch is: 49
55+
Batch id: 96
56+
Loss is: 0.0006024037564266797
57+
Epoch is: 49
58+
Batch id: 97
59+
Loss is: 0.0005297538443899917
60+
Epoch is: 49
61+
Batch id: 98
62+
Loss is: 0.0005765025171222869
63+
Epoch is: 49
64+
Batch id: 99
65+
Loss is: 0.0005290653040218895
66+
```
67+
68+
The Network learns this function very quickly, as XOR is one of the simplest
69+
distributions to hit. Since the training data is so limited, accuracy
70+
can be a bit jagged, but eventually the network smooths out.
71+
72+
### Loss over time
73+
74+
![xorloss](xor_classifier_loss.png)

examples/basic_xor_classifier/xor.cr

+72
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
# Copyright (c) 2020 Crystal Data Contributors
2+
#
3+
# MIT License
4+
#
5+
# Permission is hereby granted, free of charge, to any person obtaining
6+
# a copy of this software and associated documentation files (the
7+
# "Software"), to deal in the Software without restriction, including
8+
# without limitation the rights to use, copy, modify, merge, publish,
9+
# distribute, sublicense, and/or sell copies of the Software, and to
10+
# permit persons to whom the Software is furnished to do so, subject to
11+
# the following conditions:
12+
#
13+
# The above copyright notice and this permission notice shall be
14+
# included in all copies or substantial portions of the Software.
15+
#
16+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17+
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18+
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19+
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20+
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21+
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22+
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
23+
24+
require "../../src/num"
25+
26+
ctx = Num::Grad::Context(Tensor(Float64)).new
27+
28+
bsz = 32
29+
30+
x_train_bool = Tensor.random(0_u8...2_u8, [bsz * 100, 2])
31+
32+
y_bool = x_train_bool[..., ...1] ^ x_train_bool[..., 1...]
33+
34+
x_train = ctx.variable(x_train_bool.as_type(Float64))
35+
y = y_bool.as_type(Float64)
36+
37+
net = Num::NN::Network.new(ctx) do
38+
linear 2, 3
39+
relu
40+
linear 3, 1
41+
sgd 0.7
42+
sigmoid_cross_entropy_loss
43+
end
44+
45+
losses = [] of Float64
46+
47+
50.times do |epoch|
48+
100.times do |batch_id|
49+
offset = batch_id * 32
50+
x = x_train[offset...offset + 32]
51+
target = y[offset...offset + 32]
52+
53+
y_pred = net.forward(x)
54+
55+
loss = net.loss(y_pred, target)
56+
57+
puts "Epoch is: #{epoch}"
58+
puts "Batch id: #{batch_id}"
59+
puts "Loss is: #{loss.value.value}"
60+
losses << loss.value.value
61+
62+
loss.backprop
63+
net.optimizer.update
64+
end
65+
end
66+
67+
Num::Plot::Plot.plot do
68+
scatter (0...losses.size), losses
69+
x_label "Epochs"
70+
y_label "Loss"
71+
label "XOR Classifier Loss"
72+
end
Loading
+15
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
## Simple Scatter Plot
2+
3+
Using [PLplot](http://plplot.sourceforge.net/index.php), a fantastic library for scientific plotting, charts are extremely easy to create with Num.cr.
4+
5+
```crystal
6+
x = (0...100)
7+
y = Tensor(Float64).rand([100])
8+
9+
Num::Plot::Plot.plot do
10+
term nil
11+
scatter x, y, code: 14, color: 2
12+
end
13+
```
14+
15+
![scatter](simple_scatter.png)
+32
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
# Copyright (c) 2020 Crystal Data Contributors
2+
#
3+
# MIT License
4+
#
5+
# Permission is hereby granted, free of charge, to any person obtaining
6+
# a copy of this software and associated documentation files (the
7+
# "Software"), to deal in the Software without restriction, including
8+
# without limitation the rights to use, copy, modify, merge, publish,
9+
# distribute, sublicense, and/or sell copies of the Software, and to
10+
# permit persons to whom the Software is furnished to do so, subject to
11+
# the following conditions:
12+
#
13+
# The above copyright notice and this permission notice shall be
14+
# included in all copies or substantial portions of the Software.
15+
#
16+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17+
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18+
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19+
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20+
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21+
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22+
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
23+
24+
require "../../src/num"
25+
26+
x = (0...100)
27+
y = Tensor(Float64).rand([100])
28+
29+
Num::Plot::Plot.plot do
30+
term nil
31+
scatter x, y, code: 14, color: 2
32+
end
Loading

shard.yml

+2
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@ license: MIT
1111
dependencies:
1212
opencl:
1313
github: crystal-data/opencl.cr
14+
alea:
15+
github: nin93/alea
1416

1517
scripts:
1618
postinstall: make ext

spec/tensor/reduction_spec.cr

+31
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
require "../spec_helper"
2+
3+
describe Tensor do
4+
it "sorts a one dimensional Tensor" do
5+
a = [4, 3, 2, 1].to_tensor
6+
result = Num.sort(a)
7+
expected = [1, 2, 3, 4].to_tensor
8+
assert_array_equal(result, expected)
9+
end
10+
11+
it "sorts a strided Tensor" do
12+
a = [4, 3, 2, 1].to_tensor[{..., 2}]
13+
result = Num.sort(a)
14+
expected = [2, 4]
15+
assert_array_equal(result, expected)
16+
end
17+
18+
it "sorts a Tensor along an axis" do
19+
a = [[3, 5, 6], [1, 1, 2], [9, 2, 3]].to_tensor
20+
result = Num.sort(a, 0)
21+
expected = [[1, 1, 2], [3, 2, 3], [9, 5, 6]].to_tensor
22+
assert_array_equal(result, expected)
23+
end
24+
25+
it "sorts a strided Tensor along an axis" do
26+
a = [[3, 4, 5, 1], [2, 1, 3, 2], [4, 7, 6, 2]].to_tensor[..., {..., 2}]
27+
result = Num.sort(a, 0)
28+
expected = [[2, 3], [3, 5], [4, 6]].to_tensor
29+
assert_array_equal(result, expected)
30+
end
31+
end

src/api.cr

+21-4
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
require "./tensor/build"
22
require "./tensor/creation"
3+
require "./tensor/random"
34
require "./tensor/linalg"
45
require "./tensor/operators"
56
require "./tensor/reductions"
@@ -10,11 +11,27 @@ require "./cl_tensor/cl_tensor"
1011
require "./cl_tensor/creation"
1112
require "./cl_tensor/linalg"
1213

13-
require "./frame/frame"
14-
require "./frame/series"
15-
require "./frame/index"
16-
1714
require "./scikit/matrices"
1815
require "./scikit/clustering/kmeans"
1916

2017
require "./libs/local"
18+
require "./libs/nnpack"
19+
require "./libs/plplot"
20+
21+
require "./grad/primitives/*"
22+
require "./grad/gates_arithmetic"
23+
require "./grad/gates_blas"
24+
require "./grad/variable_ops"
25+
26+
require "./nn/primitives/*"
27+
require "./nn/layers/*"
28+
require "./nn/gates/*"
29+
require "./nn/optimizer"
30+
require "./nn/loss"
31+
require "./nn/network"
32+
33+
require "./nn/datasets/*"
34+
35+
require "./plot/internal/*"
36+
require "./plot/figures/*"
37+
require "./plot/plot"

0 commit comments

Comments
 (0)