Class: TorchModel

Inherits:
PythonModel show all
Defined in:
lib/scout/model/python/torch.rb,
lib/scout/model/python/torch/helpers.rb,
lib/scout/model/python/torch/dataloader.rb,
lib/scout/model/python/torch/introspection.rb,
lib/scout/model/python/torch/load_and_save.rb

Direct Known Subclasses

HuggingfaceModel

Defined Under Namespace

Modules: Tensor

Instance Attribute Summary collapse

Attributes inherited from ScoutModel

#directory, #options, #state

Class Method Summary collapse

Instance Method Summary collapse

Methods inherited from ScoutModel

#add, #add_list, #eval, #eval_list, #execute, #extract_features, #extract_features_list, #init, #load_method, #load_options, #load_ruby_code, #load_state, #post_process, #post_process_list, #restore, #save, #save_method, #save_options, #save_state, #state_file, #train

Constructor Details

#initializeTorchModel

Returns a new instance of TorchModel.



12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
# File 'lib/scout/model/python/torch.rb', line 12

def initialize(...)

  super(...)

  fix_options

  load_state do |state_file|
    @state = TorchModel.load(state_file, @state)
  end

  save_state do |state_file,state|
    TorchModel.save(state_file, state)
  end

  train do |features,labels|
    TorchModel.init_python
    device ||= TorchModel.device(options)
    dtype ||= TorchModel.dtype(options)
    state.to(device)
    @optimizer ||= TorchModel.optimizer(state, options[:training_args] || {})
    @criterion ||= TorchModel.optimizer(state, options[:training_args] || {})

    epochs = options[:training_args][:epochs] || 3
    batch_size = options[:batch_size]
    batch_size ||= options[:training_args][:batch_size]
    batch_size ||= 1

    inputs = TorchModel.tensor(features, device, dtype)
    #target = TorchModel.tensor(labels.collect{|v| [v] }, @device, @dtype)
    target = TorchModel.tensor(labels, device, dtype)

    Log::ProgressBar.with_bar epochs, :desc => "Training" do |bar|
      epochs.times do |i|
        optimizer.zero_grad()
        outputs = state.call(inputs)
        outputs = outputs.squeeze() if target.dim() == 1
        loss = criterion.call(outputs, target)
        loss.backward()
        optimizer.step
        Log.debug "Epoch #{i}, loss #{loss}"
        bar.tick
      end
    end
  end

  self.eval do |features,list|
    TorchModel.init_python
    device ||= TorchModel.device(options)
    dtype ||= TorchModel.dtype(options)
    state.to(device)
    state.eval

    list = [features] if features

    batch_size = options[:batch_size]
    batch_size ||= options[:training_args][:batch_size]
    batch_size ||= 1

    res = Misc.chunk(list, batch_size).inject(nil) do |acc,batch|
      tensor = TorchModel.tensor(batch, device, dtype)

      loss, chunk_res = state.call(tensor)
      tensor.del

      chunk_res = loss if chunk_res.nil?

      TorchModel::Tensor.setup(chunk_res)
      chunk_res = chunk_res.to_ruby!

      acc = acc.nil? ? chunk_res : acc + chunk_res

      acc
    end

    features ? res[0] : res
  end
end

Instance Attribute Details

#criterionObject

Returns the value of attribute criterion.



4
5
6
# File 'lib/scout/model/python/torch.rb', line 4

def criterion
  @criterion
end

#deviceObject

Returns the value of attribute device.



4
5
6
# File 'lib/scout/model/python/torch.rb', line 4

def device
  @device
end

#dtypeObject

Returns the value of attribute dtype.



4
5
6
# File 'lib/scout/model/python/torch.rb', line 4

def dtype
  @dtype
end

#optimizerObject

Returns the value of attribute optimizer.



4
5
6
# File 'lib/scout/model/python/torch.rb', line 4

def optimizer
  @optimizer
end

Class Method Details

.criterion(model, training_args = {}) ⇒ Object



55
56
57
# File 'lib/scout/model/python/torch/helpers.rb', line 55

def self.criterion(model, training_args = {})
  ScoutPython.torch.nn.MSELoss.new()
end

.device(model_options) ⇒ Object



59
60
61
62
63
64
65
66
67
68
# File 'lib/scout/model/python/torch/helpers.rb', line 59

def self.device(model_options)
  case model_options[:device]
  when String, Symbol
    ScoutPython.torch.device(model_options[:device].to_s)
  when nil
    ScoutPython.scout_ai.util.device()
  else
      model_options[:device]
  end
end

.dtype(model_options) ⇒ Object



70
71
72
73
74
75
76
77
78
79
# File 'lib/scout/model/python/torch/helpers.rb', line 70

def self.dtype(model_options)
  case model_options[:dtype]
  when String, Symbol
    ScoutPython.torch.call(model_options[:dtype])
  when nil
    nil
  else
    model_options[:dtype]
  end
end

.feature_dataset(tsv_dataset_file, elements, labels = nil, class_labels = nil) ⇒ Object



38
39
40
41
42
# File 'lib/scout/model/python/torch/dataloader.rb', line 38

def self.feature_dataset(tsv_dataset_file, elements, labels = nil, class_labels = nil)
  tsv = feature_tsv(elements, labels, class_labels)
  Open.write(tsv_dataset_file, tsv.to_s)
  tsv_dataset_file
end

.feature_tsv(elements, labels = nil, class_labels = nil) ⇒ Object



2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
# File 'lib/scout/model/python/torch/dataloader.rb', line 2

def self.feature_tsv(elements, labels = nil, class_labels = nil)
  tsv = TSV.setup({}, :key_field => "ID", :fields => ["features"], :type => :flat)
  if labels
    tsv.fields = tsv.fields + ["label"]
    labels = case class_labels
             when Array
               labels.collect{|l| class_labels.index l}
             when Hash
               inverse_class_labels = {}
               class_labels.each{|c,l| inverse_class_labels[l] = c }
               labels.collect{|l| inverse_class_labels[l]}
             else
               labels
             end
    elements.zip(labels).each_with_index do |p,i|
      features, label = p
      id = i
      if Array === features
        tsv[id] = features + [label]
      else
        tsv[id] = [features, label]
      end
    end
  else
    elements.each_with_index do |features,i|
      id = i
      if Array === features
        tsv[id] = features
      else
        tsv[id] = [features]
      end
    end
  end
  tsv
end

.freeze(layer, requires_grad = false) ⇒ Object



18
19
20
21
22
23
24
25
26
# File 'lib/scout/model/python/torch/introspection.rb', line 18

def self.freeze(layer, requires_grad=false)
  begin
    PyCall.getattr(layer, :weight).requires_grad = requires_grad
  rescue
  end
  ScoutPython.iterate(layer.children) do |layer|
    freeze(layer, requires_grad)
  end
end

.freeze_layer(state, layer, requires_grad = false) ⇒ Object



28
29
30
31
# File 'lib/scout/model/python/torch/introspection.rb', line 28

def self.freeze_layer(state, layer, requires_grad = false)
  layer = get_layer(state, layer)
  freeze(layer, requires_grad)
end

.get_layer(state, layer = nil) ⇒ Object



3
4
5
6
7
8
9
10
# File 'lib/scout/model/python/torch/introspection.rb', line 3

def self.get_layer(state, layer = nil)
  state = state.first if Array === state
  if layer.nil?
    state
  else
    layer.split(".").inject(state){|acc,l| PyCall.getattr(acc, l.to_sym) }
  end
end

.get_weights(state, layer = nil) ⇒ Object



13
14
15
# File 'lib/scout/model/python/torch/introspection.rb', line 13

def self.get_weights(state, layer = nil)
  Tensor.setup PyCall.getattr(get_layer(state, layer), :weight)
end

.init_pythonObject



36
37
38
39
40
41
42
43
44
45
46
# File 'lib/scout/model/python/torch/helpers.rb', line 36

def self.init_python
  return if defined?(@@init_python) && @@init_python
  ScoutPython.add_path Scout.python.find(:lib)
  ScoutPython.init_scout
  ScoutPython.pyimport :torch
  ScoutPython.pyimport :scout
  ScoutPython.pyimport :scout_ai
  ScoutPython.pyfrom :scout_ai, import: :util
  ScoutPython.pyfrom :torch, import: :nn
  @@init_python = true
end

.load(state_file, state = nil) ⇒ Object



42
43
44
45
46
# File 'lib/scout/model/python/torch/load_and_save.rb', line 42

def self.load(state_file, state = nil)
  state ||= TorchModel.load_architecture(state_file)
  TorchModel.load_state(state, state_file)
  state
end

.load_architecture(state_file) ⇒ Object



24
25
26
27
28
29
# File 'lib/scout/model/python/torch/load_and_save.rb', line 24

def self.load_architecture(state_file)
  model_architecture = model_architecture(state_file)
  return unless Open.exists?(model_architecture)
  Log.debug "Loading model architecture from #{model_architecture}"
  ScoutPython.torch.load(model_architecture, weights_only: false)
end

.load_state(state, state_file) ⇒ Object



11
12
13
14
15
16
# File 'lib/scout/model/python/torch/load_and_save.rb', line 11

def self.load_state(state, state_file)
  return state unless Open.exists?(state_file)
  Log.debug "Loading model state from #{state_file}"
  state.load_state_dict(ScoutPython.torch.load(state_file))
  state
end

.model_architecture(state_file) ⇒ Object



2
3
4
# File 'lib/scout/model/python/torch/load_and_save.rb', line 2

def self.model_architecture(state_file)
  state_file + '.architecture'
end

.optimizer(model, training_args = {}) ⇒ Object



48
49
50
51
52
53
# File 'lib/scout/model/python/torch/helpers.rb', line 48

def self.optimizer(model, training_args = {})
  begin
    learning_rate = training_args[:learning_rate] || 0.01
    ScoutPython.torch.optim.SGD.new(model.parameters(), lr: learning_rate)
  end
end

.save(state_file, state) ⇒ Object



37
38
39
40
# File 'lib/scout/model/python/torch/load_and_save.rb', line 37

def self.save(state_file, state)
  TorchModel.save_architecture(state, state_file)
  TorchModel.save_state(state, state_file)
end

.save_architecture(state, state_file) ⇒ Object



18
19
20
21
22
# File 'lib/scout/model/python/torch/load_and_save.rb', line 18

def self.save_architecture(state, state_file)
  model_architecture = model_architecture(state_file)
  Log.debug "Saving model architecture into #{model_architecture}"
  ScoutPython.torch.save(state, model_architecture)
end

.save_state(state, state_file) ⇒ Object



6
7
8
9
# File 'lib/scout/model/python/torch/load_and_save.rb', line 6

def self.save_state(state, state_file)
  Log.debug "Saving model state into #{state_file}"
  ScoutPython.torch.save(state.state_dict(), state_file)
end

.tensor(obj, device, dtype) ⇒ Object



81
82
83
# File 'lib/scout/model/python/torch/helpers.rb', line 81

def self.tensor(obj, device, dtype)
  TorchModel::Tensor.setup(ScoutPython.torch.tensor(obj, dtype: dtype, device: device))
end

.text_dataset(tsv_dataset_file, elements, labels = nil, class_labels = nil) ⇒ Object



44
45
46
47
48
49
50
51
52
53
54
55
# File 'lib/scout/model/python/torch/dataloader.rb', line 44

def self.text_dataset(tsv_dataset_file, elements, labels = nil, class_labels = nil)
  elements = elements.compact.collect{|e| e.gsub("\n", ' ').gsub('"', '\'') }
  tsv = feature_tsv(elements, labels, class_labels)
  tsv.fields[0] = "text"
  if labels.nil?
    tsv = tsv.to_single
  else
    tsv.type = :list
  end
  Open.write(tsv_dataset_file, tsv.to_s)
  tsv_dataset_file
end

Instance Method Details

#fix_optionsObject



6
7
8
9
10
# File 'lib/scout/model/python/torch.rb', line 6

def fix_options
  @options[:training_options] = @options.delete(:training_args) if @options.include?(:training_args)
  training_args = IndiferentHash.pull_keys(@options, :training) || {}
  @options[:training_args] = training_args
end

#freeze_layerObject



33
# File 'lib/scout/model/python/torch/introspection.rb', line 33

def freeze_layer(...); TorchModel.freeze_layer(state, ...); end

#get_layerObject



11
# File 'lib/scout/model/python/torch/introspection.rb', line 11

def get_layer(...); TorchModel.get_layer(state, ...); end

#get_weightsObject



16
# File 'lib/scout/model/python/torch/introspection.rb', line 16

def get_weights(...); TorchModel.get_weights(state, ...); end

#reset_stateObject



31
32
33
34
35
# File 'lib/scout/model/python/torch/load_and_save.rb', line 31

def reset_state
  @trainer = @state = nil
  Open.rm_rf state_file
  Open.rm_rf TorchModel.model_architecture(state_file)
end