easy_ml 0.2.0.pre.rc41 → 0.2.0.pre.rc43
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 +4 -4
- data/app/controllers/easy_ml/settings_controller.rb +1 -4
- data/app/frontend/pages/SettingsPage.tsx +1 -80
- data/app/jobs/easy_ml/batch_job.rb +45 -1
- data/app/jobs/easy_ml/compute_feature_job.rb +19 -8
- data/app/models/easy_ml/feature.rb +9 -9
- data/app/models/easy_ml/model.rb +4 -6
- data/app/models/easy_ml/model_file.rb +14 -49
- data/app/serializers/easy_ml/prediction_serializer.rb +6 -1
- data/lib/easy_ml/data/polars_reader.rb +1 -1
- data/lib/easy_ml/engine.rb +11 -0
- data/lib/easy_ml/predict.rb +25 -10
- data/lib/easy_ml/railtie/generators/migration/migration_generator.rb +1 -0
- data/lib/easy_ml/railtie/templates/migration/add_workflow_status_to_easy_ml_features.rb.tt +5 -0
- data/lib/easy_ml/railtie/templates/migration/create_easy_ml_retraining_jobs.rb.tt +1 -0
- data/lib/easy_ml/railtie/templates/migration/drop_path_from_easy_ml_model_files.rb.tt +11 -0
- data/lib/easy_ml/version.rb +1 -1
- data/public/easy_ml/assets/.vite/manifest.json +2 -2
- data/public/easy_ml/assets/assets/Application-zpGA_Q9c.css +1 -0
- data/public/easy_ml/assets/assets/entrypoints/{Application.tsx-DF5SSkYi.js → Application.tsx-jPsqOyb0.js} +87 -97
- data/public/easy_ml/assets/assets/entrypoints/Application.tsx-jPsqOyb0.js.map +1 -0
- metadata +6 -5
- data/public/easy_ml/assets/assets/Application-Cu7lNJmG.css +0 -1
- data/public/easy_ml/assets/assets/entrypoints/Application.tsx-DF5SSkYi.js.map +0 -1
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 6fc7f7afffd4e61312ab0302fb20e40a0eed3dcde05e16e9ea9ed1f523cb8681
|
4
|
+
data.tar.gz: d39511d86a3e01bc4ececcc20e1f7d0a583ac2eb07c8752c4ca2766b38318343
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 81f8594d04fcaa7274e7fc1a20ff8f67bd03599988f43e8377d10734639eb3aa48b8516f0422c7ca496774c2922e1e6042105cea72a7bad01c3bf847a553f636
|
7
|
+
data.tar.gz: d5c7cf29044fe2ac184b88ae2b68f1b9c12550157c331f263eb2e3ee79225c40245736c2531610141138e58753ffeaa257145052c02aa6fbe7598cafb0699a78
|
@@ -29,11 +29,10 @@ module EasyML
|
|
29
29
|
EasyML::Configuration.configure do |config|
|
30
30
|
config.storage = @settings.storage
|
31
31
|
config.timezone = @settings.timezone
|
32
|
-
config.s3_access_key_id = @settings.s3_access_key_id
|
33
|
-
config.s3_secret_access_key = @settings.s3_secret_access_key
|
34
32
|
config.s3_bucket = @settings.s3_bucket
|
35
33
|
config.s3_region = @settings.s3_region
|
36
34
|
config.s3_prefix = @settings.s3_prefix
|
35
|
+
config.wandb_api_key = @settings.wandb_api_key
|
37
36
|
end
|
38
37
|
flash.now[:notice] = "Settings saved."
|
39
38
|
render inertia: "pages/SettingsPage", props: {
|
@@ -47,8 +46,6 @@ module EasyML
|
|
47
46
|
params.require(:settings).permit(
|
48
47
|
:storage,
|
49
48
|
:timezone,
|
50
|
-
:s3_access_key_id,
|
51
|
-
:s3_secret_access_key,
|
52
49
|
:s3_bucket,
|
53
50
|
:s3_region,
|
54
51
|
:s3_prefix,
|
@@ -1,7 +1,7 @@
|
|
1
1
|
import React, { useState } from 'react';
|
2
2
|
import { usePage } from '@inertiajs/react'
|
3
3
|
import { useInertiaForm } from 'use-inertia-form';
|
4
|
-
import { Settings2, Save, AlertCircle, Key,
|
4
|
+
import { Settings2, Save, AlertCircle, Key, Globe2, Database } from 'lucide-react';
|
5
5
|
import { PluginSettings } from '../components/settings/PluginSettings';
|
6
6
|
|
7
7
|
interface Settings {
|
@@ -9,9 +9,6 @@ interface Settings {
|
|
9
9
|
timezone: string;
|
10
10
|
s3_bucket: string;
|
11
11
|
s3_region: string;
|
12
|
-
s3_access_key_id: string;
|
13
|
-
s3_secret_access_key: string;
|
14
|
-
wandb_api_key: string;
|
15
12
|
}
|
16
13
|
}
|
17
14
|
|
@@ -88,7 +85,6 @@ export default function SettingsPage({ settings: initialSettings }: { settings:
|
|
88
85
|
<select
|
89
86
|
id="timezone"
|
90
87
|
value={formData.settings.timezone}
|
91
|
-
|
92
88
|
onChange={(e) => setFormData({
|
93
89
|
...formData,
|
94
90
|
settings: {
|
@@ -113,7 +109,6 @@ export default function SettingsPage({ settings: initialSettings }: { settings:
|
|
113
109
|
{/* S3 Configuration */}
|
114
110
|
<div className="space-y-4">
|
115
111
|
<div className="flex items-center gap-2 mb-4">
|
116
|
-
|
117
112
|
<Database className="w-5 h-5 text-gray-500" />
|
118
113
|
<h3 className="text-lg font-medium text-gray-900">S3 Configuration</h3>
|
119
114
|
</div>
|
@@ -162,80 +157,6 @@ export default function SettingsPage({ settings: initialSettings }: { settings:
|
|
162
157
|
</select>
|
163
158
|
</div>
|
164
159
|
</div>
|
165
|
-
|
166
|
-
<div className="bg-blue-50 rounded-lg p-4">
|
167
|
-
<div className="flex gap-2">
|
168
|
-
<AlertCircle className="w-5 h-5 text-blue-500 mt-0.5" />
|
169
|
-
<div>
|
170
|
-
<h4 className="text-sm font-medium text-blue-900">AWS Credentials</h4>
|
171
|
-
<p className="mt-1 text-sm text-blue-700">
|
172
|
-
These credentials will be used as default for all S3 operations. You can override them per datasource.
|
173
|
-
</p>
|
174
|
-
</div>
|
175
|
-
</div>
|
176
|
-
</div>
|
177
|
-
|
178
|
-
<div className="grid grid-cols-2 gap-6">
|
179
|
-
<div>
|
180
|
-
<label htmlFor="accessKeyId" className="block text-sm font-medium text-gray-700 mb-1">
|
181
|
-
Access Key ID
|
182
|
-
</label>
|
183
|
-
<div className="relative">
|
184
|
-
<Key className="absolute left-3 top-1/2 transform -translate-y-1/2 w-4 h-4 text-gray-400" />
|
185
|
-
<input
|
186
|
-
type="text"
|
187
|
-
id="accessKeyId"
|
188
|
-
value={formData.settings.s3_access_key_id}
|
189
|
-
onChange={(e) => setFormData({
|
190
|
-
...formData,
|
191
|
-
settings: {
|
192
|
-
...formData.settings,
|
193
|
-
s3_access_key_id: e.target.value
|
194
|
-
}
|
195
|
-
})}
|
196
|
-
className="mt-1 block w-full pl-9 rounded-md border-gray-300 shadow-sm focus:border-blue-500 focus:ring-blue-500"
|
197
|
-
placeholder="AKIA..."
|
198
|
-
/>
|
199
|
-
</div>
|
200
|
-
</div>
|
201
|
-
|
202
|
-
<div>
|
203
|
-
<label htmlFor="secretAccessKey" className="block text-sm font-medium text-gray-700 mb-1">
|
204
|
-
Secret Access Key
|
205
|
-
</label>
|
206
|
-
<div className="relative">
|
207
|
-
<Key className="absolute left-3 top-1/2 transform -translate-y-1/2 w-4 h-4 text-gray-400" />
|
208
|
-
<input
|
209
|
-
type={showSecretKey ? 'text' : 'password'}
|
210
|
-
id="secretAccessKey"
|
211
|
-
value={formData.settings.s3_secret_access_key}
|
212
|
-
onChange={(e) => setFormData({
|
213
|
-
...formData,
|
214
|
-
settings: {
|
215
|
-
...formData.settings,
|
216
|
-
s3_secret_access_key: e.target.value
|
217
|
-
}
|
218
|
-
})}
|
219
|
-
className="mt-1 block w-full pl-9 pr-24 rounded-md border-gray-300 shadow-sm focus:border-blue-500 focus:ring-blue-500"
|
220
|
-
placeholder="Your secret key"
|
221
|
-
/>
|
222
|
-
<button
|
223
|
-
type="button"
|
224
|
-
onClick={() => setShowSecretKey(!showSecretKey)}
|
225
|
-
className="absolute right-2 top-1/2 transform -translate-y-1/2 text-sm text-gray-500 hover:text-gray-700"
|
226
|
-
>
|
227
|
-
{showSecretKey ? 'Hide' : 'Show'}
|
228
|
-
</button>
|
229
|
-
</div>
|
230
|
-
</div>
|
231
|
-
</div>
|
232
|
-
</div>
|
233
|
-
|
234
|
-
<div className="border-t border-gray-200 pt-8">
|
235
|
-
<PluginSettings
|
236
|
-
settings={formData.settings}
|
237
|
-
setData={(settings) => setFormData({ ...settings })}
|
238
|
-
/>
|
239
160
|
</div>
|
240
161
|
|
241
162
|
<div className="pt-6 border-t flex items-center justify-between">
|
@@ -12,7 +12,14 @@ module EasyML
|
|
12
12
|
# E.g. EasyML::ComputeFeatureBatchJob.enqueue_batch(features.map(&:id))
|
13
13
|
#
|
14
14
|
def enqueue_batch(args_list, batch_id = default_batch_id)
|
15
|
-
args_list = args_list.map
|
15
|
+
args_list = args_list.map do |arg|
|
16
|
+
arg = arg.is_a?(Array) ? arg : [arg]
|
17
|
+
arg.map do |arg|
|
18
|
+
arg.merge!(
|
19
|
+
batch_id: batch_id,
|
20
|
+
)
|
21
|
+
end
|
22
|
+
end
|
16
23
|
store_batch_arguments(batch_id, args_list)
|
17
24
|
|
18
25
|
args_list.each do |args|
|
@@ -22,8 +29,45 @@ module EasyML
|
|
22
29
|
batch_id
|
23
30
|
end
|
24
31
|
|
32
|
+
def enqueue_ordered_batches(args_list)
|
33
|
+
parent_id = get_parent_batch_id(args_list)
|
34
|
+
store_batch_arguments(parent_id, args_list)
|
35
|
+
|
36
|
+
batch = args_list.first
|
37
|
+
rest = args_list[1..]
|
38
|
+
|
39
|
+
rest.map do |batch|
|
40
|
+
Resque.redis.rpush("batch:#{parent_id}:remaining", batch.to_json)
|
41
|
+
end
|
42
|
+
|
43
|
+
enqueue_batch(batch)
|
44
|
+
end
|
45
|
+
|
46
|
+
def enqueue_next_batch(caller, parent_id)
|
47
|
+
next_batch = Resque.redis.lpop("batch:#{parent_id}:remaining")
|
48
|
+
payload = Resque.decode(next_batch)
|
49
|
+
|
50
|
+
caller.enqueue_batch(payload)
|
51
|
+
end
|
52
|
+
|
53
|
+
def next_batch?(parent_id)
|
54
|
+
batches_remaining(parent_id) > 0
|
55
|
+
end
|
56
|
+
|
57
|
+
def batches_remaining(parent_id)
|
58
|
+
Resque.redis.llen("batch:#{parent_id}:remaining")
|
59
|
+
end
|
60
|
+
|
61
|
+
def cleanup_batch(parent_id)
|
62
|
+
Resque.redis.del("batch:#{parent_id}:remaining")
|
63
|
+
end
|
64
|
+
|
25
65
|
private
|
26
66
|
|
67
|
+
def get_parent_batch_id(args_list)
|
68
|
+
args_list.dup.flatten.first.dig(:parent_batch_id)
|
69
|
+
end
|
70
|
+
|
27
71
|
# Store batch arguments in Redis
|
28
72
|
def store_batch_arguments(batch_id, args_list)
|
29
73
|
redis_key = "#{batch(batch_id)}:original_args"
|
@@ -5,7 +5,6 @@ module EasyML
|
|
5
5
|
@queue = :easy_ml
|
6
6
|
|
7
7
|
def self.perform(batch_id, options = {})
|
8
|
-
puts "processing batch_id #{batch_id}"
|
9
8
|
options.symbolize_keys!
|
10
9
|
feature_id = options.dig(:feature_id)
|
11
10
|
feature = EasyML::Feature.find(feature_id)
|
@@ -18,13 +17,12 @@ module EasyML
|
|
18
17
|
end
|
19
18
|
|
20
19
|
begin
|
20
|
+
feature.update(workflow_status: :analyzing) if feature.workflow_status == :ready
|
21
21
|
feature.fit_batch(options.merge!(batch_id: batch_id))
|
22
22
|
rescue => e
|
23
|
-
puts "Error computing feature: #{e.message}"
|
24
23
|
EasyML::Feature.transaction do
|
25
24
|
return if dataset.reload.workflow_status == :failed
|
26
25
|
|
27
|
-
puts "Logging error"
|
28
26
|
feature.update(workflow_status: :failed)
|
29
27
|
dataset.update(workflow_status: :failed)
|
30
28
|
build_error_with_context(dataset, e, batch_id, feature)
|
@@ -42,12 +40,25 @@ module EasyML
|
|
42
40
|
|
43
41
|
def self.after_batch_hook(batch_id, *args)
|
44
42
|
puts "After batch!"
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
43
|
+
batch_args = fetch_batch_arguments(batch_id).flatten.map(&:symbolize_keys)
|
44
|
+
feature_ids = batch_args.pluck(:feature_id).uniq
|
45
|
+
parent_id = batch_args.pluck(:parent_batch_id).first
|
46
|
+
|
47
|
+
feature = EasyML::Feature.find_by(id: feature_ids.first)
|
48
|
+
|
49
|
+
if feature.failed?
|
50
|
+
dataset.features.where(workflow_status: :analyzing).update_all(workflow_status: :ready)
|
51
|
+
return BatchJob.cleanup_batch(parent_id)
|
52
|
+
end
|
53
|
+
|
54
|
+
feature.after_fit
|
49
55
|
|
50
|
-
|
56
|
+
if BatchJob.next_batch?(parent_id)
|
57
|
+
BatchJob.enqueue_next_batch(self, parent_id)
|
58
|
+
else
|
59
|
+
dataset = EasyML::Feature.find_by(id: feature_ids.first).dataset
|
60
|
+
dataset.after_fit_features
|
61
|
+
end
|
51
62
|
end
|
52
63
|
|
53
64
|
private
|
@@ -196,12 +196,15 @@ module EasyML
|
|
196
196
|
max_id = df[primary_key.last].max
|
197
197
|
end
|
198
198
|
|
199
|
-
(min_id..max_id).step(batch_size).map do |batch_start|
|
199
|
+
(min_id..max_id).step(batch_size).map.with_index do |batch_start, idx|
|
200
200
|
batch_end = [batch_start + batch_size, max_id + 1].min - 1
|
201
201
|
{
|
202
202
|
feature_id: id,
|
203
203
|
batch_start: batch_start,
|
204
204
|
batch_end: batch_end,
|
205
|
+
batch_number: feature_position,
|
206
|
+
subbatch_number: idx,
|
207
|
+
parent_batch_id: Random.uuid,
|
205
208
|
}
|
206
209
|
end
|
207
210
|
end
|
@@ -211,17 +214,16 @@ module EasyML
|
|
211
214
|
end
|
212
215
|
|
213
216
|
def fit(features: [self], async: false)
|
214
|
-
# Sort features by position to ensure they're processed in order
|
215
|
-
features.update_all(workflow_status: :analyzing)
|
216
217
|
ordered_features = features.sort_by(&:feature_position)
|
217
|
-
jobs = ordered_features.
|
218
|
+
jobs = ordered_features.map(&:build_batches)
|
218
219
|
|
219
220
|
if async
|
220
|
-
EasyML::ComputeFeatureJob.
|
221
|
+
EasyML::ComputeFeatureJob.enqueue_ordered_batches(jobs)
|
221
222
|
else
|
222
|
-
jobs.each do |job|
|
223
|
+
jobs.flatten.each do |job|
|
223
224
|
EasyML::ComputeFeatureJob.perform(nil, job)
|
224
225
|
end
|
226
|
+
features.each(&:after_fit) unless features.any?(&:failed?)
|
225
227
|
end
|
226
228
|
end
|
227
229
|
|
@@ -393,13 +395,11 @@ module EasyML
|
|
393
395
|
updates = {
|
394
396
|
applied_at: Time.current,
|
395
397
|
needs_fit: false,
|
398
|
+
workflow_status: :ready,
|
396
399
|
}.compact
|
397
400
|
update!(updates)
|
398
401
|
end
|
399
402
|
|
400
|
-
def fully_processed?
|
401
|
-
end
|
402
|
-
|
403
403
|
private
|
404
404
|
|
405
405
|
def bulk_update_positions(features)
|
data/app/models/easy_ml/model.rb
CHANGED
@@ -250,6 +250,7 @@ module EasyML
|
|
250
250
|
bump_version(force: true)
|
251
251
|
path = model_file.full_path(version)
|
252
252
|
full_path = adapter.save_model_file(path)
|
253
|
+
puts "saving model to #{full_path}"
|
253
254
|
model_file.upload(full_path)
|
254
255
|
|
255
256
|
model_file.save
|
@@ -266,6 +267,7 @@ module EasyML
|
|
266
267
|
end
|
267
268
|
|
268
269
|
def cleanup
|
270
|
+
puts "keeping files #{files_to_keep}"
|
269
271
|
get_model_file&.cleanup(files_to_keep)
|
270
272
|
end
|
271
273
|
|
@@ -488,13 +490,9 @@ module EasyML
|
|
488
490
|
end
|
489
491
|
|
490
492
|
def root_dir
|
491
|
-
|
493
|
+
relative_dir = read_attribute(:root_dir) || default_root_dir
|
492
494
|
|
493
|
-
|
494
|
-
EasyML::Engine.root_dir.join(persisted).to_s
|
495
|
-
else
|
496
|
-
default_root_dir
|
497
|
-
end
|
495
|
+
EasyML::Engine.root_dir.join(relative_dir).to_s
|
498
496
|
end
|
499
497
|
|
500
498
|
def default_root_dir
|
@@ -33,12 +33,23 @@ module EasyML
|
|
33
33
|
s3_region: s3_region,
|
34
34
|
s3_access_key_id: s3_access_key_id,
|
35
35
|
s3_secret_access_key: s3_secret_access_key,
|
36
|
-
root_dir:
|
36
|
+
root_dir: root_dir,
|
37
37
|
)
|
38
38
|
end
|
39
39
|
|
40
40
|
def root_dir
|
41
|
-
model.root_dir
|
41
|
+
Pathname.new(model.root_dir)
|
42
|
+
end
|
43
|
+
|
44
|
+
def model_root
|
45
|
+
File.expand_path("..", root_dir.to_s)
|
46
|
+
end
|
47
|
+
|
48
|
+
def full_path(filename = nil)
|
49
|
+
filename = self.filename if filename.nil?
|
50
|
+
return nil if filename.nil?
|
51
|
+
|
52
|
+
root_dir.join(filename).to_s
|
42
53
|
end
|
43
54
|
|
44
55
|
def exist?
|
@@ -58,33 +69,7 @@ module EasyML
|
|
58
69
|
|
59
70
|
def upload(path)
|
60
71
|
synced_file.upload(path)
|
61
|
-
|
62
|
-
end
|
63
|
-
|
64
|
-
def set_path(path)
|
65
|
-
path = get_full_path(path)
|
66
|
-
basename = Pathname.new(path).basename.to_s
|
67
|
-
unless path.start_with?(full_dir)
|
68
|
-
new_path = File.join(full_dir, basename).to_s
|
69
|
-
FileUtils.mkdir_p(Pathname.new(new_path).dirname.to_s)
|
70
|
-
FileUtils.cp(path, new_path)
|
71
|
-
path = new_path
|
72
|
-
end
|
73
|
-
self.filename = basename
|
74
|
-
self.path = get_relative_path(path)
|
75
|
-
end
|
76
|
-
|
77
|
-
def get_full_path(path)
|
78
|
-
path = path.to_s
|
79
|
-
|
80
|
-
path = Rails.root.join(path) unless path.match?(Regexp.new(Rails.root.to_s))
|
81
|
-
path
|
82
|
-
end
|
83
|
-
|
84
|
-
def get_relative_path(path)
|
85
|
-
path = path.to_s
|
86
|
-
path = path.to_s.split(Rails.root.to_s).last
|
87
|
-
path.to_s.split("/")[0..-2].reject(&:empty?).join("/")
|
72
|
+
update(filename: Pathname.new(path).basename.to_s)
|
88
73
|
end
|
89
74
|
|
90
75
|
def download
|
@@ -98,26 +83,6 @@ module EasyML
|
|
98
83
|
Digest::SHA256.file(full_path).hexdigest
|
99
84
|
end
|
100
85
|
|
101
|
-
def full_path(filename = nil)
|
102
|
-
filename = self.filename if filename.nil?
|
103
|
-
return nil if filename.nil?
|
104
|
-
return nil if relative_dir.nil?
|
105
|
-
|
106
|
-
Rails.root.join(relative_dir, filename).to_s
|
107
|
-
end
|
108
|
-
|
109
|
-
def relative_dir
|
110
|
-
root_dir.to_s.gsub(Regexp.new(Rails.root.to_s), "").gsub(%r{^/}, "")
|
111
|
-
end
|
112
|
-
|
113
|
-
def full_dir
|
114
|
-
Rails.root.join(relative_dir).to_s
|
115
|
-
end
|
116
|
-
|
117
|
-
def model_root
|
118
|
-
File.expand_path("..", full_dir)
|
119
|
-
end
|
120
|
-
|
121
86
|
def cleanup!
|
122
87
|
[model_root].each do |dir|
|
123
88
|
EasyML::Support::FileRotate.new(dir, []).cleanup(extension_allowlist)
|
@@ -5,7 +5,12 @@ module EasyML
|
|
5
5
|
include JSONAPI::Serializer
|
6
6
|
|
7
7
|
attribute :prediction do |object|
|
8
|
-
object.prediction_value
|
8
|
+
case object.prediction_value
|
9
|
+
when Hash
|
10
|
+
object.prediction_value.symbolize_keys.dig(:value)
|
11
|
+
when Numeric
|
12
|
+
object.prediction_value
|
13
|
+
end
|
9
14
|
end
|
10
15
|
|
11
16
|
attributes :id,
|
@@ -225,7 +225,7 @@ module EasyML
|
|
225
225
|
|
226
226
|
# Filter out any datetime columns, and use maybe_convert_date to convert later
|
227
227
|
date_cols = (filtered[:dtypes] || {}).select { |k, v| v.class == Polars::Datetime }.keys
|
228
|
-
filtered[:dtypes] = (filtered[:dtypes] || {}).reject { |k, v| v.class == Polars::Datetime }
|
228
|
+
filtered[:dtypes] = (filtered[:dtypes] || {}).reject { |k, v| v.class == Polars::Datetime }.compact.to_h
|
229
229
|
filtered = filtered.select { |k, _| supported_params.include?(k) }
|
230
230
|
return filtered, date_cols
|
231
231
|
end
|
data/lib/easy_ml/engine.rb
CHANGED
@@ -88,6 +88,17 @@ module EasyML
|
|
88
88
|
end
|
89
89
|
end
|
90
90
|
|
91
|
+
initializer "easy_ml.configure_secrets" do
|
92
|
+
EasyML::Configuration.configure do |config|
|
93
|
+
raise "S3_ACCESS_KEY_ID is missing. Set ENV['S3_ACCESS_KEY_ID']" unless ENV["S3_ACCESS_KEY_ID"]
|
94
|
+
raise "S3_SECRET_ACCESS_KEY is missing. Set ENV['S3_SECRET_ACCESS_KEY']" unless ENV["S3_SECRET_ACCESS_KEY"]
|
95
|
+
|
96
|
+
config.s3_access_key_id = ENV["S3_ACCESS_KEY_ID"]
|
97
|
+
config.s3_secret_access_key = ENV["S3_SECRET_ACCESS_KEY"]
|
98
|
+
config.wandb_api_key = ENV["WANDB_API_KEY"] if ENV["WANDB_API_KEY"]
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
91
102
|
initializer "easy_ml.setup_generators" do |app|
|
92
103
|
generators_path = EasyML::Engine.root.join("lib/easy_ml/railtie/generators")
|
93
104
|
generators_dirs = Dir[File.join(generators_path, "**", "*.rb")]
|
data/lib/easy_ml/predict.rb
CHANGED
@@ -10,23 +10,38 @@ module EasyML
|
|
10
10
|
@models = {}
|
11
11
|
end
|
12
12
|
|
13
|
-
def self.predict(model_name, df)
|
13
|
+
def self.predict(model_name, df, serialize: false)
|
14
14
|
if df.is_a?(Hash)
|
15
15
|
df = Polars::DataFrame.new(df)
|
16
16
|
end
|
17
|
-
raw_input = df.to_hashes
|
17
|
+
raw_input = df.to_hashes
|
18
18
|
df = instance.normalize(model_name, df)
|
19
|
+
normalized_input = df.to_hashes
|
19
20
|
preds = instance.predict(model_name, df)
|
20
21
|
current_version = instance.get_model(model_name)
|
21
22
|
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
23
|
+
output = preds.zip(raw_input, normalized_input).map do |pred, raw, norm|
|
24
|
+
EasyML::Prediction.create!(
|
25
|
+
model: current_version.model,
|
26
|
+
model_history: current_version,
|
27
|
+
prediction_type: current_version.model.task,
|
28
|
+
prediction_value: pred,
|
29
|
+
raw_input: raw,
|
30
|
+
normalized_input: norm,
|
31
|
+
)
|
32
|
+
end
|
33
|
+
|
34
|
+
output = if output.is_a?(Array) && output.count == 1
|
35
|
+
output.first
|
36
|
+
else
|
37
|
+
output
|
38
|
+
end
|
39
|
+
|
40
|
+
if serialize
|
41
|
+
EasyML::PredictionSerializer.new(output).serializable_hash
|
42
|
+
else
|
43
|
+
output
|
44
|
+
end
|
30
45
|
end
|
31
46
|
|
32
47
|
def self.train(model_name, tuner: nil, evaluator: nil)
|
@@ -4,5 +4,10 @@ class AddWorkflowStatusToEasyMLFeatures < ActiveRecord::Migration[<%= ActiveReco
|
|
4
4
|
add_column :easy_ml_features, :workflow_status, :string
|
5
5
|
add_index :easy_ml_features, :workflow_status
|
6
6
|
end
|
7
|
+
|
8
|
+
unless column_exists?(:easy_ml_feature_histories, :workflow_status)
|
9
|
+
add_column :easy_ml_feature_histories, :workflow_status, :string
|
10
|
+
add_index :easy_ml_feature_histories, :workflow_status
|
11
|
+
end
|
7
12
|
end
|
8
13
|
end
|
@@ -0,0 +1,11 @@
|
|
1
|
+
class DropPathFromEasyMLModelFiles < ActiveRecord::Migration[<%= ActiveRecord::Migration.current_version %>]
|
2
|
+
def change
|
3
|
+
if column_exists?(:easy_ml_model_files, :path)
|
4
|
+
remove_column :easy_ml_model_files, :path
|
5
|
+
end
|
6
|
+
|
7
|
+
if column_exists?(:easy_ml_model_file_histories, :path)
|
8
|
+
remove_column :easy_ml_model_file_histories, :path
|
9
|
+
end
|
10
|
+
end
|
11
|
+
end
|
data/lib/easy_ml/version.rb
CHANGED
@@ -1,11 +1,11 @@
|
|
1
1
|
{
|
2
2
|
"entrypoints/Application.tsx": {
|
3
|
-
"file": "assets/entrypoints/Application.tsx-
|
3
|
+
"file": "assets/entrypoints/Application.tsx-jPsqOyb0.js",
|
4
4
|
"name": "entrypoints/Application.tsx",
|
5
5
|
"src": "entrypoints/Application.tsx",
|
6
6
|
"isEntry": true,
|
7
7
|
"css": [
|
8
|
-
"assets/Application-
|
8
|
+
"assets/Application-zpGA_Q9c.css"
|
9
9
|
]
|
10
10
|
}
|
11
11
|
}
|