affinity_propagation 0.1.0 → 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +5 -5
- data/.gitignore +2 -1
- data/affinity_propagation.gemspec +2 -1
- data/lib/affinity_propagation/calculator.rb +86 -50
- data/lib/affinity_propagation/version.rb +1 -1
- metadata +9 -11
- data/Gemfile.lock +0 -47
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
|
-
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 43c3b9be3a0ad3d3ac9a4c428194f66af06d9add87d9390fc70267d92631f04d
|
4
|
+
data.tar.gz: 91c8248f1dce46cd08a220114d5f74d01e2dad90e4ac4e5469c326f0cf60f0bb
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 598aecb0dc0f1593dbe032527cb22e72903b75a1e5c9721a99145ce490a175e54ff6817db729328652aefbf6b93f1cd460fa6358a2cda167c5f0bdebfc607a89
|
7
|
+
data.tar.gz: fed4c922d48fd640889b98c1434cf39006b43628429406bd369a856e0b9c75df086b6b71d84f802753e7ed879fb451e0d8334ffde81914f65fb64a06f057cc9e
|
data/.gitignore
CHANGED
@@ -42,7 +42,7 @@ build-iPhoneSimulator/
|
|
42
42
|
|
43
43
|
# for a library or gem, you might want to ignore these files since the code is
|
44
44
|
# intended to run in multiple environments; otherwise, check them in:
|
45
|
-
|
45
|
+
Gemfile.lock
|
46
46
|
# .ruby-version
|
47
47
|
# .ruby-gemset
|
48
48
|
|
@@ -52,3 +52,4 @@ build-iPhoneSimulator/
|
|
52
52
|
# IDEs
|
53
53
|
.idea
|
54
54
|
|
55
|
+
.byebug_history
|
@@ -28,5 +28,6 @@ Gem::Specification.new do |spec|
|
|
28
28
|
spec.add_development_dependency "bundler", "~> 1.15"
|
29
29
|
spec.add_development_dependency "rake", "~> 10.0"
|
30
30
|
spec.add_development_dependency "rspec", "~> 3.0"
|
31
|
-
|
31
|
+
|
32
|
+
spec.add_runtime_dependency "concurrent-ruby", "~> 1.1.5"
|
32
33
|
end
|
@@ -1,27 +1,8 @@
|
|
1
1
|
require 'matrix'
|
2
|
+
require 'concurrent'
|
3
|
+
require 'thread'
|
2
4
|
|
3
5
|
module AffinityPropagation
|
4
|
-
refine Array do
|
5
|
-
def median
|
6
|
-
relevant_elements = if size % 2 == 0
|
7
|
-
# Even number of items in this list => let's get the middle two and return their mean
|
8
|
-
slice(size / 2, 2)
|
9
|
-
else
|
10
|
-
slice(size / 2, 1)
|
11
|
-
end
|
12
|
-
|
13
|
-
relevant_elements.sum / relevant_elements.size
|
14
|
-
end
|
15
|
-
end
|
16
|
-
|
17
|
-
refine Matrix do
|
18
|
-
def to_matlab
|
19
|
-
output = row_vectors.map(&:to_a).map { |row| row.join(', ') }.join('; ')
|
20
|
-
|
21
|
-
"[#{output}]"
|
22
|
-
end
|
23
|
-
end
|
24
|
-
|
25
6
|
class Calculator
|
26
7
|
using AffinityPropagation
|
27
8
|
|
@@ -33,7 +14,7 @@ module AffinityPropagation
|
|
33
14
|
@data = data
|
34
15
|
@lambda = lambda
|
35
16
|
|
36
|
-
raise 'no block
|
17
|
+
raise 'no block provided to calculate similarities within data!' unless block_given?
|
37
18
|
|
38
19
|
@similarities = similarity_matrix(&block)
|
39
20
|
reset
|
@@ -70,25 +51,47 @@ module AffinityPropagation
|
|
70
51
|
end
|
71
52
|
|
72
53
|
def run(iterations: 100, stable_iterations: 10)
|
73
|
-
|
54
|
+
while @total_iterations < iterations && @stable_cluster_iterations < stable_iterations
|
55
|
+
iterate
|
56
|
+
|
57
|
+
yield(@total_iterations, @stable_cluster_iterations) if block_given?
|
58
|
+
end
|
74
59
|
end
|
75
60
|
|
76
61
|
private
|
77
62
|
|
63
|
+
def median(array)
|
64
|
+
relevant_elements = if array.size % 2 == 0
|
65
|
+
# Even number of items in this list => let's get the middle two and return their mean
|
66
|
+
array.sort.slice(array.size / 2, 2)
|
67
|
+
else
|
68
|
+
array.sort.slice(array.size / 2, 1)
|
69
|
+
end
|
70
|
+
|
71
|
+
relevant_elements.sum / relevant_elements.size
|
72
|
+
end
|
73
|
+
|
78
74
|
def similarity_matrix(&block)
|
79
75
|
similarity_array = []
|
80
|
-
|
76
|
+
similarities_future = Matrix.build(@data.size, @data.size) do |row_idx, col_idx|
|
81
77
|
exemplar = @data[row_idx]
|
82
78
|
datum = @data[col_idx]
|
83
79
|
|
84
|
-
|
85
|
-
similarity_array <<
|
86
|
-
|
80
|
+
similarity_future = Concurrent::Future.execute(executor: :fast) { block.call(datum, exemplar)}
|
81
|
+
similarity_array << similarity_future
|
82
|
+
|
83
|
+
similarity_future
|
87
84
|
end
|
88
85
|
|
89
|
-
|
86
|
+
while similarity_array.any?(&:pending?)
|
87
|
+
sleep 0.1
|
88
|
+
end
|
89
|
+
|
90
|
+
similarity_array.map!(&:value)
|
91
|
+
similarities = similarities_future.map(&:value)
|
92
|
+
|
93
|
+
median_similarity = median(similarity_array)
|
90
94
|
|
91
|
-
# Matrices can't be modified once created so we have to use this hack
|
92
95
|
(0...@data.size).each { |idx| similarities.send(:[]=, idx, idx, median_similarity) }
|
93
96
|
|
94
97
|
similarities
|
@@ -99,49 +102,82 @@ module AffinityPropagation
|
|
99
102
|
end
|
100
103
|
|
101
104
|
def responsibility_matrix
|
102
|
-
|
105
|
+
responsibility_futures = []
|
106
|
+
|
107
|
+
responsibilities_future = Matrix.build(@similarities.row_count, @similarities.column_count) do |row_idx, col_idx|
|
103
108
|
exemplar_idx = row_idx
|
104
109
|
datum_idx = col_idx
|
105
110
|
|
111
|
+
current_similarity = @similarities[row_idx, col_idx]
|
112
|
+
current_responsibility = @responsibilities[exemplar_idx, datum_idx]
|
113
|
+
|
106
114
|
availability_column = @availabilities.column(col_idx).to_a
|
107
|
-
availability_column.slice!(exemplar_idx)
|
108
115
|
similarity_column = @similarities.column(col_idx).to_a
|
109
|
-
similarity_column.slice!(exemplar_idx)
|
110
116
|
|
111
|
-
|
112
|
-
|
117
|
+
responsibility_future = Concurrent::Future.execute(executor: :fast) do
|
118
|
+
availability_column.slice!(exemplar_idx)
|
119
|
+
similarity_column.slice!(exemplar_idx)
|
120
|
+
|
121
|
+
availability_plus_similarity = []
|
122
|
+
availability_column.zip(similarity_column) { |data| availability_plus_similarity << data.sum }
|
123
|
+
|
124
|
+
dampen(current_similarity - availability_plus_similarity.max, current_responsibility)
|
125
|
+
end
|
126
|
+
|
127
|
+
responsibility_futures << responsibility_future
|
128
|
+
responsibility_future
|
129
|
+
end
|
113
130
|
|
114
|
-
|
131
|
+
while responsibility_futures.any?(&:pending?)
|
132
|
+
sleep 0.1
|
115
133
|
end
|
134
|
+
|
135
|
+
responsibilities_future.map(&:value)
|
116
136
|
end
|
117
137
|
|
118
138
|
def availability_matrix
|
119
|
-
|
139
|
+
availability_futures = []
|
140
|
+
|
141
|
+
availabilities_future = Matrix.build(@responsibilities.row_count, @responsibilities.column_count) do |row_idx, col_idx|
|
120
142
|
exemplar_idx = row_idx
|
121
143
|
datum_idx = col_idx
|
122
144
|
responsibility_column = @responsibilities.row(exemplar_idx).to_a
|
123
145
|
|
124
|
-
|
125
|
-
|
126
|
-
responsibility_column.slice!(exemplar_idx)
|
146
|
+
current_availability = @availabilities[exemplar_idx, datum_idx]
|
147
|
+
current_responsibility = @responsibilities[exemplar_idx, exemplar_idx]
|
127
148
|
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
if datum_idx > exemplar_idx
|
132
|
-
# Slice out the datum index first since in this case it won't affect the exemplar index
|
133
|
-
responsibility_column.slice!(datum_idx)
|
149
|
+
availability_future = Concurrent::Future.execute(executor: :fast) do
|
150
|
+
if exemplar_idx == datum_idx
|
151
|
+
# self-availability
|
134
152
|
responsibility_column.slice!(exemplar_idx)
|
153
|
+
|
154
|
+
dampen(responsibility_column.inject(0) { |sum, item| sum += [0, item].max }, current_availability)
|
135
155
|
else
|
136
|
-
|
137
|
-
|
156
|
+
self_responsibility = current_responsibility
|
157
|
+
if datum_idx > exemplar_idx
|
158
|
+
# Slice out the datum index first since in this case it won't affect the exemplar index
|
159
|
+
responsibility_column.slice!(datum_idx)
|
160
|
+
responsibility_column.slice!(exemplar_idx)
|
161
|
+
else
|
162
|
+
responsibility_column.slice!(exemplar_idx)
|
163
|
+
responsibility_column.slice!(datum_idx)
|
164
|
+
end
|
165
|
+
|
166
|
+
responsibility_column_sum = responsibility_column.inject(0) { |sum, item| sum += [0, item].max }
|
167
|
+
|
168
|
+
dampen([0, self_responsibility + responsibility_column_sum].min, current_availability)
|
138
169
|
end
|
170
|
+
end
|
139
171
|
|
140
|
-
|
172
|
+
availability_futures << availability_future
|
173
|
+
availability_future
|
174
|
+
end
|
141
175
|
|
142
|
-
|
143
|
-
|
176
|
+
while availability_futures.any?(&:pending?)
|
177
|
+
sleep 0.1
|
144
178
|
end
|
179
|
+
|
180
|
+
availabilities_future.map(&:value)
|
145
181
|
end
|
146
182
|
|
147
183
|
def identify_raw_clusters
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: affinity_propagation
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.2.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Shahbaz Javeed
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2019-08-22 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|
@@ -53,19 +53,19 @@ dependencies:
|
|
53
53
|
- !ruby/object:Gem::Version
|
54
54
|
version: '3.0'
|
55
55
|
- !ruby/object:Gem::Dependency
|
56
|
-
name:
|
56
|
+
name: concurrent-ruby
|
57
57
|
requirement: !ruby/object:Gem::Requirement
|
58
58
|
requirements:
|
59
|
-
- - "
|
59
|
+
- - "~>"
|
60
60
|
- !ruby/object:Gem::Version
|
61
|
-
version:
|
62
|
-
type: :
|
61
|
+
version: 1.1.5
|
62
|
+
type: :runtime
|
63
63
|
prerelease: false
|
64
64
|
version_requirements: !ruby/object:Gem::Requirement
|
65
65
|
requirements:
|
66
|
-
- - "
|
66
|
+
- - "~>"
|
67
67
|
- !ruby/object:Gem::Version
|
68
|
-
version:
|
68
|
+
version: 1.1.5
|
69
69
|
description: "\n Affinity Propagation is a clustering algorithm that does not require
|
70
70
|
pre-specifying the number of clusters like\n k-means and other similar algorithms
|
71
71
|
do. This is a ruby implementation of the original version defined by Frey and\n
|
@@ -83,7 +83,6 @@ files:
|
|
83
83
|
- ".ruby-version"
|
84
84
|
- ".travis.yml"
|
85
85
|
- Gemfile
|
86
|
-
- Gemfile.lock
|
87
86
|
- LICENSE.txt
|
88
87
|
- README.md
|
89
88
|
- Rakefile
|
@@ -112,8 +111,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
112
111
|
- !ruby/object:Gem::Version
|
113
112
|
version: '0'
|
114
113
|
requirements: []
|
115
|
-
|
116
|
-
rubygems_version: 2.6.11
|
114
|
+
rubygems_version: 3.0.6
|
117
115
|
signing_key:
|
118
116
|
specification_version: 4
|
119
117
|
summary: An implementation of the affinity propagation clustering algorithm by Frey
|
data/Gemfile.lock
DELETED
@@ -1,47 +0,0 @@
|
|
1
|
-
PATH
|
2
|
-
remote: .
|
3
|
-
specs:
|
4
|
-
affinity_propagation (0.1.0)
|
5
|
-
|
6
|
-
GEM
|
7
|
-
remote: https://rubygems.org/
|
8
|
-
specs:
|
9
|
-
activesupport (5.1.4)
|
10
|
-
concurrent-ruby (~> 1.0, >= 1.0.2)
|
11
|
-
i18n (~> 0.7)
|
12
|
-
minitest (~> 5.1)
|
13
|
-
tzinfo (~> 1.1)
|
14
|
-
concurrent-ruby (1.0.5)
|
15
|
-
diff-lcs (1.3)
|
16
|
-
i18n (0.8.6)
|
17
|
-
minitest (5.10.3)
|
18
|
-
rake (10.5.0)
|
19
|
-
rspec (3.6.0)
|
20
|
-
rspec-core (~> 3.6.0)
|
21
|
-
rspec-expectations (~> 3.6.0)
|
22
|
-
rspec-mocks (~> 3.6.0)
|
23
|
-
rspec-core (3.6.0)
|
24
|
-
rspec-support (~> 3.6.0)
|
25
|
-
rspec-expectations (3.6.0)
|
26
|
-
diff-lcs (>= 1.2.0, < 2.0)
|
27
|
-
rspec-support (~> 3.6.0)
|
28
|
-
rspec-mocks (3.6.0)
|
29
|
-
diff-lcs (>= 1.2.0, < 2.0)
|
30
|
-
rspec-support (~> 3.6.0)
|
31
|
-
rspec-support (3.6.0)
|
32
|
-
thread_safe (0.3.6)
|
33
|
-
tzinfo (1.2.3)
|
34
|
-
thread_safe (~> 0.1)
|
35
|
-
|
36
|
-
PLATFORMS
|
37
|
-
ruby
|
38
|
-
|
39
|
-
DEPENDENCIES
|
40
|
-
activesupport
|
41
|
-
affinity_propagation!
|
42
|
-
bundler (~> 1.15)
|
43
|
-
rake (~> 10.0)
|
44
|
-
rspec (~> 3.0)
|
45
|
-
|
46
|
-
BUNDLED WITH
|
47
|
-
1.15.4
|