libmf 0.2.1 → 0.2.5

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 1d7744d5436815acc2609c8c39566f5704513248b5fe0b7ddc1cc4fbd28c81e9
4
- data.tar.gz: 0bcd219cc9360b0e9daefe418e2a14fc46969ab174db5ea93717c837e309c697
3
+ metadata.gz: 499cc67221be5e767caa7257e69c23a2b17c537c5950e2eeebb7de21d5bec1f0
4
+ data.tar.gz: 9345dc3804dfc928bbdb03b588e64f20fbb3ddb5a3674b65972c122842bef60e
5
5
  SHA512:
6
- metadata.gz: 49d31c33bea909e1a3a0a8cdebc6cce76b2cf19debc11420d76ad2016763961f376df56bdbba16e6a60e72a8014a2b44547045b2385f44b6c96f48f3f6148643
7
- data.tar.gz: 3f264d87a4cc727634ac8b2cb5dc87a56a44c3fba7d77093c83ca3623437be6024c04b1fe154275fd3a14a6736bf6703ea2fae9b984570b5c4629af3b5c710f3
6
+ metadata.gz: 6cd517a3e7eb418390012352a62de72aa9e3a4077b08c5d8602e89a9a8af0dcdc654a51359641e374d263804189a814afffa8e2f55db52ef7506e222e2143e5f
7
+ data.tar.gz: 5dad62dd22cd82499d4f0c2b2c06792945c50065a2517d89baeec8d00c62e2628d62d3702cc3964344c3e437f4281afd1f79f58291efca12f3c788e5ed07d0e1
data/CHANGELOG.md CHANGED
@@ -1,3 +1,22 @@
1
+ ## 0.2.5 (2021-10-18)
2
+
3
+ - Added named loss functions
4
+ - Added `Matrix` class
5
+ - Improved error checking for `fit`, `cv`, `save_model`, and `load_model`
6
+
7
+ ## 0.2.4 (2021-08-05)
8
+
9
+ - Fixed memory leak
10
+
11
+ ## 0.2.3 (2021-03-14)
12
+
13
+ - Added ARM shared library for Linux
14
+
15
+ ## 0.2.2 (2021-02-04)
16
+
17
+ - Reduced allocations
18
+ - Improved ARM detection
19
+
1
20
  ## 0.2.1 (2020-12-28)
2
21
 
3
22
  - Added ARM shared library for Mac
data/LICENSE.txt CHANGED
@@ -1,7 +1,7 @@
1
1
  BSD 3-Clause License
2
2
 
3
3
  Copyright (c) 2014-2015, The LIBMF Project
4
- Copyright (c) 2019-2020, Andrew Kane
4
+ Copyright (c) 2019-2021, Andrew Kane
5
5
  All rights reserved.
6
6
 
7
7
  Redistribution and use in source and binary forms, with or without
data/README.md CHANGED
@@ -90,7 +90,7 @@ Pass parameters - default values below
90
90
 
91
91
  ```ruby
92
92
  Libmf::Model.new(
93
- loss: 0, # loss function
93
+ loss: :real_l2, # loss function
94
94
  factors: 8, # number of latent factors
95
95
  threads: 12, # number of threads used
96
96
  bins: 25, # number of bins
@@ -111,21 +111,21 @@ Libmf::Model.new(
111
111
 
112
112
  For real-valued matrix factorization
113
113
 
114
- - 0 - squared error (L2-norm)
115
- - 1 - absolute error (L1-norm)
116
- - 2 - generalized KL-divergence
114
+ - `:real_l2` - squared error (L2-norm)
115
+ - `:real_l1` - absolute error (L1-norm)
116
+ - `:real_kl` - generalized KL-divergence
117
117
 
118
118
  For binary matrix factorization
119
119
 
120
- - 5 - logarithmic error
121
- - 6 - squared hinge loss
122
- - 7 - hinge loss
120
+ - `:binary_log` - logarithmic error
121
+ - `:binary_l2` - squared hinge loss
122
+ - `:binary_l1` - hinge loss
123
123
 
124
124
  For one-class matrix factorization
125
125
 
126
- - 10 - row-oriented pair-wise logarithmic loss
127
- - 11 - column-oriented pair-wise logarithmic loss
128
- - 12 - squared error (L2-norm)
126
+ - `:one_class_row` - row-oriented pair-wise logarithmic loss
127
+ - `:one_class_col` - column-oriented pair-wise logarithmic loss
128
+ - `:one_class_l2` - squared error (L2-norm)
129
129
 
130
130
  ## Performance
131
131
 
data/lib/libmf/ffi.rb CHANGED
@@ -43,15 +43,22 @@ module Libmf
43
43
  :b, :float,
44
44
  :p, :pointer,
45
45
  :q, :pointer
46
+
47
+ def self.release(pointer)
48
+ unless pointer.null?
49
+ ref = ::FFI::MemoryPointer.new(:pointer).write_pointer(pointer)
50
+ FFI.mf_destroy_model(ref)
51
+ end
52
+ end
46
53
  end
47
54
 
48
55
  attach_function :mf_get_default_param, [], Parameter.by_value
49
56
  attach_function :mf_read_problem, [:string], Problem.by_value
50
57
  attach_function :mf_save_model, [Model.by_ref, :string], :int
51
- attach_function :mf_load_model, [:string], Model.by_ref
52
- attach_function :mf_destroy_model, [Model.by_ref], :void
53
- attach_function :mf_train, [Problem.by_ref, Parameter.by_value], Model.by_ref
54
- attach_function :mf_train_with_validation, [Problem.by_ref, Problem.by_ref, Parameter.by_value], Model.by_ref
58
+ attach_function :mf_load_model, [:string], Model.auto_ptr
59
+ attach_function :mf_destroy_model, [:pointer], :void
60
+ attach_function :mf_train, [Problem.by_ref, Parameter.by_value], Model.auto_ptr
61
+ attach_function :mf_train_with_validation, [Problem.by_ref, Problem.by_ref, Parameter.by_value], Model.auto_ptr
55
62
  attach_function :mf_predict, [Model.by_ref, :int, :int], :float
56
63
  attach_function :mf_cross_validation, [Problem.by_ref, :int, Parameter.by_value], :double
57
64
  end
@@ -0,0 +1,13 @@
1
+ module Libmf
2
+ class Matrix
3
+ attr_reader :data
4
+
5
+ def initialize
6
+ @data = []
7
+ end
8
+
9
+ def push(row_index, column_index, value)
10
+ @data << [row_index, column_index, value]
11
+ end
12
+ end
13
+ end
data/lib/libmf/model.rb CHANGED
@@ -14,6 +14,7 @@ module Libmf
14
14
  else
15
15
  FFI.mf_train(train_set, param)
16
16
  end
17
+ raise Error, "fit failed" if @model.null?
17
18
 
18
19
  nil
19
20
  end
@@ -24,15 +25,20 @@ module Libmf
24
25
 
25
26
  def cv(data, folds: 5)
26
27
  problem = create_problem(data)
27
- FFI.mf_cross_validation(problem, folds, param)
28
+ # TODO update fork to differentiate between bad parameters and zero error
29
+ res = FFI.mf_cross_validation(problem, folds, param)
30
+ raise Error, "cv failed" if res == 0
31
+ res
28
32
  end
29
33
 
30
34
  def save_model(path)
31
- FFI.mf_save_model(model, path)
35
+ status = FFI.mf_save_model(model, path)
36
+ raise Error, "Cannot save model" if status != 0
32
37
  end
33
38
 
34
39
  def load_model(path)
35
40
  @model = FFI.mf_load_model(path)
41
+ raise Error, "Cannot open model" if @model.null?
36
42
  end
37
43
 
38
44
  def rows
@@ -80,6 +86,22 @@ module Libmf
80
86
  def param
81
87
  param = FFI.mf_get_default_param
82
88
  options = @options.dup
89
+
90
+ if options[:loss].is_a?(Symbol)
91
+ loss_map = {
92
+ real_l2: 0,
93
+ real_l1: 1,
94
+ real_kl: 2,
95
+ binary_log: 5,
96
+ binary_l2: 6,
97
+ binary_l1: 7,
98
+ one_class_row: 10,
99
+ one_class_col: 11,
100
+ one_class_l2: 12
101
+ }
102
+ options[:loss] = loss_map[options[:loss]] || (raise ArgumentError, "Unknown loss")
103
+ end
104
+
83
105
  # silence insufficient blocks warning with default params
84
106
  options[:bins] ||= 25 unless options[:nr_bins]
85
107
  options[:copy_data] = false unless options.key?(:copy_data)
@@ -107,14 +129,19 @@ module Libmf
107
129
  return FFI.mf_read_problem(File.expand_path(data))
108
130
  end
109
131
 
132
+ if data.is_a?(Matrix)
133
+ data = data.data
134
+ end
135
+
110
136
  raise Error, "No data" if data.empty?
111
137
 
112
138
  # TODO do in C for better performance
113
139
  # can use FIX2INT() and RFLOAT_VALUE() instead of pack
140
+ # and write directly to C string
114
141
  buffer = String.new
142
+ pack_format = "iif"
115
143
  data.each do |row|
116
- row[0, 2].pack("i*".freeze, buffer: buffer)
117
- row[2, 1].pack("f".freeze, buffer: buffer)
144
+ row.pack(pack_format, buffer: buffer)
118
145
  end
119
146
 
120
147
  r = ::FFI::MemoryPointer.new(FFI::Node, data.size)
data/lib/libmf/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  module Libmf
2
- VERSION = "0.2.1"
2
+ VERSION = "0.2.5"
3
3
  end
data/lib/libmf.rb CHANGED
@@ -2,6 +2,7 @@
2
2
  require "ffi"
3
3
 
4
4
  # modules
5
+ require "libmf/matrix"
5
6
  require "libmf/model"
6
7
  require "libmf/version"
7
8
 
@@ -14,12 +15,18 @@ module Libmf
14
15
  lib_name =
15
16
  if Gem.win_platform?
16
17
  "mf.dll"
17
- elsif RbConfig::CONFIG["arch"] =~ /arm64-darwin/i
18
- "libmf.arm64.dylib"
19
18
  elsif RbConfig::CONFIG["host_os"] =~ /darwin/i
20
- "libmf.dylib"
19
+ if RbConfig::CONFIG["host_cpu"] =~ /arm/i
20
+ "libmf.arm64.dylib"
21
+ else
22
+ "libmf.dylib"
23
+ end
21
24
  else
22
- "libmf.so"
25
+ if RbConfig::CONFIG["host_cpu"] =~ /aarch64/i
26
+ "libmf.arm64.so"
27
+ else
28
+ "libmf.so"
29
+ end
23
30
  end
24
31
  vendor_lib = File.expand_path("../vendor/#{lib_name}", __dir__)
25
32
  self.ffi_lib = [vendor_lib]
Binary file
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: libmf
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.1
4
+ version: 0.2.5
5
5
  platform: ruby
6
6
  authors:
7
7
  - Andrew Kane
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2020-12-29 00:00:00.000000000 Z
11
+ date: 2021-10-19 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: ffi
@@ -25,7 +25,7 @@ dependencies:
25
25
  - !ruby/object:Gem::Version
26
26
  version: '0'
27
27
  description:
28
- email: andrew@chartkick.com
28
+ email: andrew@ankane.org
29
29
  executables: []
30
30
  extensions: []
31
31
  extra_rdoc_files: []
@@ -35,12 +35,14 @@ files:
35
35
  - README.md
36
36
  - lib/libmf.rb
37
37
  - lib/libmf/ffi.rb
38
+ - lib/libmf/matrix.rb
38
39
  - lib/libmf/model.rb
39
40
  - lib/libmf/version.rb
40
41
  - vendor/COPYRIGHT
41
42
  - vendor/demo/real_matrix.te.txt
42
43
  - vendor/demo/real_matrix.tr.txt
43
44
  - vendor/libmf.arm64.dylib
45
+ - vendor/libmf.arm64.so
44
46
  - vendor/libmf.dylib
45
47
  - vendor/libmf.so
46
48
  - vendor/mf.dll
@@ -63,7 +65,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
63
65
  - !ruby/object:Gem::Version
64
66
  version: '0'
65
67
  requirements: []
66
- rubygems_version: 3.2.3
68
+ rubygems_version: 3.2.22
67
69
  signing_key:
68
70
  specification_version: 4
69
71
  summary: Large-scale sparse matrix factorization for Ruby