affinity_propagation 0.1.0 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|