Skip to content

Commit 9747ebe

Browse files
committed
Code Comment updates
1 parent 7e2ae5e commit 9747ebe

File tree

6 files changed

+183
-121
lines changed

6 files changed

+183
-121
lines changed

lib/learn_kit/regression/linear.ex

Lines changed: 28 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ defmodule LearnKit.Regression.Linear do
88
alias LearnKit.Regression.Linear
99

1010
use Linear.Calculations
11+
use LearnKit.Regression.Score
1112

1213
@type factors :: [number]
1314
@type results :: [number]
@@ -80,11 +81,12 @@ defmodule LearnKit.Regression.Linear do
8081
coefficients: coefficients
8182
}
8283

83-
def fit(%Linear{factors: factors, results: results}, options \\ []) when is_list(options) do
84+
def fit(linear = %Linear{factors: factors, results: results}, options \\ [])
85+
when is_list(options) do
8486
coefficients =
8587
Keyword.merge([method: ""], options)
8688
|> define_method_for_fit()
87-
|> do_fit(factors, results)
89+
|> do_fit(linear)
8890

8991
%Linear{factors: factors, results: results, coefficients: coefficients}
9092
end
@@ -112,13 +114,33 @@ defmodule LearnKit.Regression.Linear do
112114
"""
113115
@spec predict(%Linear{coefficients: coefficients}, list) :: {:ok, list}
114116

115-
def predict(%Linear{coefficients: coefficients}, samples) when is_list(samples) do
117+
def predict(linear = %Linear{coefficients: _}, samples) when is_list(samples) do
116118
{
117119
:ok,
118-
Enum.map(samples, fn sample -> predict_sample(sample, coefficients) end)
120+
Enum.map(samples, fn sample -> predict(linear, sample) end)
119121
}
120122
end
121123

124+
@doc """
125+
Predict using the linear model
126+
127+
## Parameters
128+
129+
- predictor: %LearnKit.Regression.Linear{}
130+
- sample: Sample variable
131+
132+
## Examples
133+
134+
iex> predictor |> LearnKit.Regression.Linear.predict(4)
135+
{:ok, 14.5}
136+
137+
"""
138+
@spec predict(%Linear{coefficients: coefficients}, list) :: {:ok, list}
139+
140+
def predict(%Linear{coefficients: [alpha, beta]}, sample) do
141+
sample * beta + alpha
142+
end
143+
122144
@doc """
123145
Returns the coefficient of determination R^2 of the prediction
124146
@@ -135,10 +157,10 @@ defmodule LearnKit.Regression.Linear do
135157
@spec score(%Linear{factors: factors, results: results, coefficients: coefficients}) ::
136158
{:ok, number}
137159

138-
def score(%Linear{factors: factors, results: results, coefficients: coefficients}) do
160+
def score(linear = %Linear{factors: _, results: _, coefficients: _}) do
139161
{
140162
:ok,
141-
calculate_score(coefficients, factors, results)
163+
calculate_score(linear)
142164
}
143165
end
144166
end

lib/learn_kit/regression/linear/calculations.ex

Lines changed: 8 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,12 @@ defmodule LearnKit.Regression.Linear.Calculations do
44
"""
55

66
alias LearnKit.Math
7+
alias LearnKit.Regression.Linear
78

89
defmacro __using__(_opts) do
910
quote do
10-
defp do_fit(method, factors, results) when method == "gradient descent" do
11+
defp do_fit(method, %Linear{factors: factors, results: results})
12+
when method == "gradient descent" do
1113
gradient_descent_iteration(
1214
[:rand.uniform(), :rand.uniform()],
1315
0.0001,
@@ -18,7 +20,7 @@ defmodule LearnKit.Regression.Linear.Calculations do
1820
)
1921
end
2022

21-
defp do_fit(_, factors, results) do
23+
defp do_fit(_, %Linear{factors: factors, results: results}) do
2224
beta =
2325
Math.correlation(factors, results) * Math.standard_deviation(results) /
2426
Math.standard_deviation(factors)
@@ -27,48 +29,15 @@ defmodule LearnKit.Regression.Linear.Calculations do
2729
[alpha, beta]
2830
end
2931

30-
defp predict_sample(sample, [alpha, beta]) do
31-
sample * beta + alpha
32-
end
33-
34-
defp calculate_score([], _, _), do: raise("There was no fit for model")
35-
36-
defp calculate_score(coefficients, factors, results) do
37-
1.0 -
38-
sum_of_squared_errors(coefficients, factors, results) / total_sum_of_squares(results)
39-
end
40-
41-
defp total_sum_of_squares(list) do
42-
mean_list = Math.mean(list)
43-
Enum.reduce(list, 0, fn x, acc -> acc + :math.pow(x - mean_list, 2) end)
44-
end
45-
46-
defp sum_of_squared_errors(coefficients, factors, results) do
47-
Enum.zip(factors, results)
48-
|> Enum.reduce(0, fn {xi, yi}, acc ->
49-
acc + squared_prediction_error(coefficients, xi, yi)
50-
end)
51-
end
52-
53-
defp squared_prediction_error(coefficients, x, y) do
54-
coefficients
55-
|> prediction_error(x, y)
56-
|> :math.pow(2)
57-
end
58-
59-
defp squared_error_gradient(coefficients, x, y) do
60-
error_variable = prediction_error(coefficients, x, y)
32+
defp squared_error_gradient(linear, x, y) do
33+
error_variable = prediction_error(linear, x, y)
6134

6235
[
6336
-2 * error_variable,
6437
-2 * error_variable * x
6538
]
6639
end
6740

68-
defp prediction_error(coefficients, x, y) do
69-
y - predict_sample(x, coefficients)
70-
end
71-
7241
defp gradient_descent_iteration(_, _, min_theta, _, _, iterations_with_no_improvement)
7342
when iterations_with_no_improvement >= 100,
7443
do: min_theta
@@ -92,7 +61,7 @@ defmodule LearnKit.Regression.Linear.Calculations do
9261
data
9362
|> Enum.shuffle()
9463
|> Enum.reduce(theta, fn {xi, yi}, acc ->
95-
gradient_i = squared_error_gradient(acc, xi, yi)
64+
gradient_i = squared_error_gradient(%Linear{coefficients: theta}, xi, yi)
9665
acc |> Math.vector_subtraction(alpha |> Math.scalar_multiply(gradient_i))
9766
end)
9867

@@ -109,7 +78,7 @@ defmodule LearnKit.Regression.Linear.Calculations do
10978
defp check_value(data, min_value, theta, min_theta, iterations_with_no_improvement, alpha) do
11079
value =
11180
Enum.reduce(data, 0, fn {xi, yi}, acc ->
112-
acc + squared_prediction_error(theta, xi, yi)
81+
acc + squared_prediction_error(%Linear{coefficients: theta}, xi, yi)
11382
end)
11483

11584
cond do

lib/learn_kit/regression/polynomial.ex

Lines changed: 17 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,12 @@ defmodule LearnKit.Regression.Polynomial do
66
defstruct factors: [], results: [], coefficients: [], degree: 2
77

88
alias LearnKit.Regression.Polynomial
9+
use Polynomial.Calculations
910

1011
@type factors :: [number]
1112
@type results :: [number]
1213
@type coefficients :: [number]
13-
@type degree :: number
14+
@type degree :: integer
1415

1516
@doc """
1617
Creates polynomial predictor with data_set
@@ -26,6 +27,12 @@ defmodule LearnKit.Regression.Polynomial do
2627
%LearnKit.Regression.Polynomial{factors: [1, 2, 3, 4], results: [3, 6, 10, 15], coefficients: [], degree: 2}
2728
2829
"""
30+
@spec new(factors, results) :: %Polynomial{
31+
factors: factors,
32+
results: results,
33+
coefficients: [],
34+
degree: 2
35+
}
2936
def new(factors, results) when is_list(factors) and is_list(results) do
3037
%Polynomial{factors: factors, results: results}
3138
end
@@ -65,6 +72,12 @@ defmodule LearnKit.Regression.Polynomial do
6572
}
6673
6774
"""
75+
@spec fit(%Polynomial{factors: factors, results: results}) :: %Polynomial{
76+
factors: factors,
77+
results: results,
78+
coefficients: coefficients,
79+
degree: degree
80+
}
6881
def fit(%Polynomial{factors: factors, results: results}, options \\ []) do
6982
degree = options[:degree] || 2
7083
matrix = matrix(factors, degree)
@@ -87,13 +100,10 @@ defmodule LearnKit.Regression.Polynomial do
87100
{:ok, [20.999999999999723, 27.999999999999574]}
88101
89102
"""
103+
@spec predict(%Polynomial{coefficients: coefficients, degree: degree}, list) :: {:ok, list}
90104
def predict(polynomial = %Polynomial{coefficients: _, degree: _}, samples)
91105
when is_list(samples) do
92-
{:ok,
93-
Enum.map(samples, fn sample ->
94-
{:ok, prediction} = predict(polynomial, sample)
95-
prediction
96-
end)}
106+
{:ok, do_predict(polynomial, samples)}
97107
end
98108

99109
@doc """
@@ -110,63 +120,9 @@ defmodule LearnKit.Regression.Polynomial do
110120
{:ok, 20.999999999999723}
111121
112122
"""
123+
@spec predict(%Polynomial{coefficients: coefficients, degree: degree}, number) :: {:ok, number}
113124
def predict(%Polynomial{coefficients: coefficients, degree: degree}, sample) do
114125
ordered_coefficients = coefficients |> Enum.reverse()
115126
{:ok, substitute_coefficients(ordered_coefficients, sample, degree, 0.0)}
116127
end
117-
118-
defp matrix_line(1, factors, degree) do
119-
power_ofs = Enum.to_list(1..degree)
120-
121-
[Enum.count(factors)] ++
122-
Enum.map(power_ofs, fn factor ->
123-
sum_x_with_k(factors, factor, 0.0)
124-
end)
125-
end
126-
127-
defp matrix_line(line, factors, degree) do
128-
line_factor = line - 1
129-
power_ofs = Enum.to_list(line_factor..(degree + line_factor))
130-
131-
Enum.map(power_ofs, fn factor ->
132-
sum_x_with_k(factors, factor, 0.0)
133-
end)
134-
end
135-
136-
defp matrix(factors, degree) do
137-
lines = Enum.to_list(1..(degree + 1))
138-
139-
Enum.map(lines, fn line ->
140-
matrix_line(line, factors, degree)
141-
end)
142-
end
143-
144-
defp substitute_coefficients([], _, _, sum), do: sum
145-
146-
defp substitute_coefficients([coefficient | tail], x, k, sum) do
147-
sum = sum + :math.pow(x, k) * coefficient
148-
substitute_coefficients(tail, x, k - 1, sum)
149-
end
150-
151-
defp sum_x_with_k([x | tail], k, sum) do
152-
sum = sum + :math.pow(x, k)
153-
sum_x_with_k(tail, k, sum)
154-
end
155-
156-
defp sum_x_with_k([], _, sum), do: sum
157-
158-
defp sum_x_y_with_k([], [], _degree, sum), do: [sum]
159-
160-
defp sum_x_y_with_k([x | xtail], [y | ytail], degree, sum) do
161-
exponent = degree - 1
162-
sum = sum + :math.pow(x, exponent) * y
163-
sum_x_y_with_k(xtail, ytail, degree, sum)
164-
end
165-
166-
def x_y_matrix(_, _, 0, matrix), do: matrix |> Enum.reverse()
167-
168-
def x_y_matrix(xs, ys, degree, matrix) do
169-
matrix = matrix ++ [sum_x_y_with_k(xs, ys, degree, 0.0)]
170-
x_y_matrix(xs, ys, degree - 1, matrix)
171-
end
172128
end
Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
defmodule LearnKit.Regression.Polynomial.Calculations do
2+
@moduledoc """
3+
Module for fit functions
4+
"""
5+
6+
defmacro __using__(_opts) do
7+
quote do
8+
defp do_predict(polynomial, samples) do
9+
Enum.map(samples, fn sample ->
10+
{:ok, prediction} = predict(polynomial, sample)
11+
prediction
12+
end)
13+
end
14+
15+
defp matrix_line(1, factors, degree) do
16+
power_ofs = Enum.to_list(1..degree)
17+
18+
[Enum.count(factors) | sum_of_x_i_with_k(power_ofs, factors)]
19+
end
20+
21+
defp matrix_line(line, factors, degree) do
22+
line_factor = line - 1
23+
power_ofs = Enum.to_list(line_factor..(degree + line_factor))
24+
sum_of_x_i_with_k(power_ofs, factors)
25+
end
26+
27+
defp matrix(factors, degree) do
28+
lines = Enum.to_list(1..(degree + 1))
29+
30+
Enum.map(lines, fn line ->
31+
matrix_line(line, factors, degree)
32+
end)
33+
end
34+
35+
def sum_of_x_i_with_k(ks, factors) do
36+
Enum.map(ks, fn factor ->
37+
sum_x_with_k(factors, factor, 0.0)
38+
end)
39+
end
40+
41+
defp substitute_coefficients([], _, _, sum), do: sum
42+
43+
defp substitute_coefficients([coefficient | tail], x, k, sum) do
44+
sum = sum + :math.pow(x, k) * coefficient
45+
substitute_coefficients(tail, x, k - 1, sum)
46+
end
47+
48+
defp sum_x_with_k([x | tail], k, sum) do
49+
sum = sum + :math.pow(x, k)
50+
sum_x_with_k(tail, k, sum)
51+
end
52+
53+
defp sum_x_with_k([], _, sum), do: sum
54+
55+
defp sum_x_y_with_k([], [], _degree, sum), do: [sum]
56+
57+
defp sum_x_y_with_k([x | xtail], [y | ytail], degree, sum) do
58+
exponent = degree - 1
59+
sum = sum + :math.pow(x, exponent) * y
60+
sum_x_y_with_k(xtail, ytail, degree, sum)
61+
end
62+
63+
def x_y_matrix(_, _, 0, matrix), do: matrix |> Enum.reverse()
64+
65+
def x_y_matrix(xs, ys, degree, matrix) do
66+
matrix = matrix ++ [sum_x_y_with_k(xs, ys, degree, 0.0)]
67+
x_y_matrix(xs, ys, degree - 1, matrix)
68+
end
69+
end
70+
end
71+
end

lib/learn_kit/regression/score.ex

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
defmodule LearnKit.Regression.Score do
2+
@moduledoc """
3+
Module for fit functions
4+
"""
5+
6+
alias LearnKit.Math
7+
8+
defmacro __using__(_opts) do
9+
quote do
10+
defp calculate_score(%_{coefficients: []}, _, _), do: raise("There was no fit for model")
11+
12+
defp calculate_score(regression = %_{coefficients: _, factors: _, results: results}) do
13+
1.0 - sum_of_squared_errors(regression) / total_sum_of_squares(results)
14+
end
15+
16+
defp prediction_error(regression, x, y) do
17+
y - predict(regression, x)
18+
end
19+
20+
defp sum_of_squared_errors(
21+
regression = %_{coefficients: _, factors: factors, results: results}
22+
) do
23+
Enum.zip(factors, results)
24+
|> Enum.reduce(0, fn {xi, yi}, acc ->
25+
acc + squared_prediction_error(regression, xi, yi)
26+
end)
27+
end
28+
29+
defp total_sum_of_squares(list) do
30+
mean_list = Math.mean(list)
31+
Enum.reduce(list, 0, fn x, acc -> acc + :math.pow(x - mean_list, 2) end)
32+
end
33+
34+
defp squared_prediction_error(regression = %_{coefficients: coefficients}, x, y) do
35+
regression
36+
|> prediction_error(x, y)
37+
|> :math.pow(2)
38+
end
39+
end
40+
end
41+
end

0 commit comments

Comments
 (0)