tensor_stream 1.0.6 → 1.0.7

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,114 @@
1
+ require 'tensor_stream/utils/py_ports'
2
+ ##
3
+ # ruby port of https://github.com/tensorflow/tensorflow/blob/r1.13/tensorflow/python/ops/embedding_ops.py
4
+ #
5
+ module TensorStream
6
+ module EmbeddingLookup
7
+ include TensorStream::PyPorts
8
+
9
+ ##
10
+ # Looks up `ids` in a list of embedding tensors.
11
+ def embedding_lookup(params, ids, partition_strategy: "mod", name: nil, validate_indices: true, max_norm: nil)
12
+ _embedding_lookup_and_transform(params, ids, partition_strategy: partition_strategy, name: name, max_norm: max_norm, transform_fn: nil)
13
+ end
14
+
15
+ ##
16
+ # Helper function for embedding_lookup and _compute_sampled_logits.
17
+ def _embedding_lookup_and_transform(params, ids, partition_strategy: "mod", name: nil, max_norm: nil, transform_fn: nil)
18
+ raise TensorStream::ValueError, "Need at least one param" if params.nil?
19
+
20
+ params = [params] unless params.is_a?(Array)
21
+
22
+ TensorStream.name_scope(name, "embedding_lookup", values: params + [ids]) do |name|
23
+ np = params.size
24
+ ids = TensorStream.convert_to_tensor(ids, name: "ids")
25
+ if (np == 1) && (transform_fn.nil? || (ids.shape.size == 1))
26
+ result = nil
27
+ TensorStream.colocate_with(params[0]) do
28
+ result = _clip(TensorStream.gather(params[0], ids, name: name), ids, max_norm)
29
+ result = transform_fn.call(result) if transform_fn
30
+ end
31
+
32
+ return TensorStream.identity(result)
33
+ else
34
+ flat_ids = TensorStream.reshape(ids, [-1])
35
+ original_indices = TensorStream.range(TensorStream.size(flat_ids))
36
+
37
+ p_assignments = nil
38
+ new_ids = nil
39
+
40
+ if partition_strategy == "mod"
41
+ p_assignments = flat_ids % np
42
+ new_ids = floor_div(flat_ids, np)
43
+ elsif partition_strategy == "div"
44
+ raise "not yet supported!"
45
+ else
46
+ raise TensorStream::ValueError, "Unrecognized partition strategy: " + partition_strategy
47
+ end
48
+
49
+ p_assignments = TensorStream.cast(p_assignments, :int32)
50
+ gather_ids = TensorStream.dynamic_partition(new_ids, p_assignments, np)
51
+ pindices = TensorStream.dynamic_partition(original_indices, p_assignments, np)
52
+ partitioned_result = []
53
+ (0...np).each do |p|
54
+ pids = gather_ids[p]
55
+ result = nil
56
+ TensorStream.colocate_with(params[p]) do
57
+ result = TensorStream.gather(params[p], pids)
58
+ if transform_fn
59
+ # If transform_fn is provided, the clip_by_norm precedes
60
+ # the transform and hence must be co-located. See below
61
+ # for the counterpart if transform_fn is not proveded.
62
+ result = transform_fn.call(_clip(result, pids, max_norm))
63
+ end
64
+ end
65
+ partitioned_result << result
66
+ end
67
+ ret = TensorStream.dynamic_stitch(pindices, partitioned_result, name: name)
68
+
69
+ if transform_fn.nil?
70
+ element_shape_s = params[0].shape[1..-1]
71
+ params[1..-1].each { |p| element_shape_s = element_shape_s.merge_with(p.shape[1..-1]) }
72
+ else
73
+ element_shape_s = ret.shape[1..-1]
74
+ end
75
+
76
+ # Compute the dynamic element shape.
77
+ element_shape_d = if element_shape_s.fully_defined?
78
+ element_shape_s
79
+ elsif transform_fn.nil?
80
+ # It's important that we compute params[0].shape on the right device
81
+ # to avoid data motion.
82
+ TensorStream.colocate_with(params[0]) do
83
+ params_shape = TensorStream.shape(params[0])
84
+ params_shape[1..-1]
85
+ end
86
+ else
87
+ TensorStream.shape(ret)[1..-1]
88
+ end
89
+ ret = TensorStream.reshape(ret, TensorStream.concat([TensorStream.shape(ids), element_shape_d], 0))
90
+ ret = _clip(ret, ids, max_norm) unless transform_fn
91
+ ret
92
+ end
93
+ end
94
+ end
95
+
96
+ def _clip(params, ids, max_norm)
97
+ return params if max_norm.nil?
98
+
99
+ ids_rank, ids_static = _rank(ids)
100
+ params_rank, params_static = _rank(params)
101
+
102
+ TensorStream.clip_by_norm(params, max_norm, axes: ids_static && params_static ? (ids_rank...params_rank).to_a : TensorStream.range(ids_rank, params_rank))
103
+ end
104
+
105
+ def _rank(x)
106
+ rank = TensorStream.convert_to_tensor(x).shape.ndims
107
+ if rank
108
+ [rank, false]
109
+ else
110
+ [TensorStream.rank(x), false]
111
+ end
112
+ end
113
+ end
114
+ end
@@ -1,7 +1,10 @@
1
+ require 'tensor_stream/nn/embedding_lookup'
1
2
  module TensorStream
2
3
  # High level machine learning functions
3
4
  class NN
4
5
  extend TensorStream::OpHelper
6
+ extend TensorStream::EmbeddingLookup
7
+ extend TensorStream::Maths::MathFunctions
5
8
 
6
9
  class << self
7
10
  def softmax(logits, axis: nil, name: nil)
@@ -2,7 +2,7 @@ class TensorStream::OpMaker
2
2
  attr_reader :operation, :description, :parameters,
3
3
  :options, :gradient, :check_types,
4
4
  :supports_broadcast, :data_type_coercion,
5
- :aliases, :custom, :infer_type_proc, :exclude,
5
+ :aliases, :custom, :custom_post, :infer_type_proc, :exclude,
6
6
  :data_type_block
7
7
 
8
8
  def initialize(op)
@@ -16,6 +16,7 @@ class TensorStream::OpMaker
16
16
  @description = []
17
17
  @aliases = []
18
18
  @custom = []
19
+ @custom_post = []
19
20
  @infer_type_proc = lambda { |tensor|
20
21
  next nil if tensor.inputs[0].nil?
21
22
  next tensor.inputs[0].shape.shape if tensor.inputs.size == 1
@@ -32,6 +33,10 @@ class TensorStream::OpMaker
32
33
  @custom << custom_code
33
34
  end
34
35
 
36
+ def add_custom_post(custom_code)
37
+ @custom_post << custom_code
38
+ end
39
+
35
40
  def self.scan
36
41
  op_files = Dir[File.join(File.dirname(__FILE__), "ops", "*.rb")]
37
42
  op_files.each { |file|
@@ -111,7 +116,14 @@ class TensorStream::OpMaker
111
116
  custom.each do |c|
112
117
  body << c
113
118
  end
114
- body << "_op(:#{operation}, #{(expand_params(false) + options_call).join(', ')})"
119
+ if custom_post.empty?
120
+ body << "_op(:#{operation}, #{(expand_params(false) + options_call).join(', ')})"
121
+ else
122
+ body << "result = _op(:#{operation}, #{(expand_params(false) + options_call).join(', ')})"
123
+ end
124
+ custom_post.each do |c|
125
+ body << c
126
+ end
115
127
  body.map { |line| " #{line}"}.join("\n")
116
128
  end
117
129
 
@@ -184,7 +196,7 @@ class TensorStream::OpMaker
184
196
  end
185
197
 
186
198
  def options_call
187
- @options.map { |k, v|
199
+ @options.reject { |k, v| v.dig(:options, :exclude) }.map { |k, v|
188
200
  if v.dig(:options, :alias)
189
201
  "#{v.dig(:options, :alias)}: #{k}"
190
202
  else
@@ -195,6 +195,15 @@ module TensorStream
195
195
  end
196
196
  end
197
197
 
198
+ ##
199
+ # Partitions data into num_partitions tensors using indices from partitions
200
+ def dynamic_partition(data, partitions, num_partitions, name: nil)
201
+ result = _op(:dynamic_partition, data, partitions, num_partitions: num_partitions, name: nil)
202
+ num_partitions.times.map do |index|
203
+ result[index]
204
+ end
205
+ end
206
+
198
207
  def split(value, num_or_size_splits, axis: 0, num: nil, name: "split")
199
208
  value = convert_to_tensor(value)
200
209
  num_or_size_splits = convert_to_tensor(num_or_size_splits)
@@ -524,6 +533,9 @@ module TensorStream
524
533
  _op(:squeeze, value, axis: axis, name: nil)
525
534
  end
526
535
 
536
+ def clip_by_norm(tensor, clip_norm, axes: nil, name: nil)
537
+ end
538
+
527
539
  ##
528
540
  # Computes the difference between two lists of numbers or strings.
529
541
  # Given a list x and a list y, this operation returns a list out that represents all values
@@ -0,0 +1,11 @@
1
+ TensorStream::OpMaker.define_operation :rsqrt do |op|
2
+ op.what_it_does "Computes reciprocal of square root of x element-wise."
3
+
4
+ op.parameter :input_a, "tensor X", validate: 'FLOATING_POINT_TYPES'
5
+ op.option :name, "Optional name", :nil
6
+
7
+ op.define_gradient do |grad, node, params|
8
+ # Returns -0.5 * grad * conj(y)^3.
9
+ i_op(:rsqrt_grad, node, grad)
10
+ end
11
+ end
@@ -0,0 +1,24 @@
1
+ TensorStream::OpMaker.define_operation :strided_slice do |op|
2
+ op.what_it_does "Extracts a strided slice of a tensor "
3
+ op.what_it_does "this op extracts a slice of size `(end-begin)/stride`
4
+ from the given `input_` tensor. Starting at the location specified by `begin`
5
+ the slice continues by adding `stride` to the index until all dimensions are
6
+ not less than `end`.
7
+ Note that a stride can be negative, which causes a reverse slice."
8
+
9
+ op.parameter :input, "A tensor"
10
+ op.parameter :_begin, "start index"
11
+ op.parameter :_end, "end index"
12
+ op.parameter :strides, "end index", :nil
13
+ op.option :name, "Optional name", :nil
14
+
15
+ op.define_gradient do |grad, node, params|
16
+ input, b_index, e_index, strides = params
17
+ x = ts.shape(input, out_type: node.inputs[0].data_type)
18
+
19
+ _op(:strided_slice_grad, x, b_index, e_index, strides, grad)
20
+ end
21
+
22
+ op.define_shape do |tensor|
23
+ end
24
+ end
@@ -7,14 +7,16 @@ TensorStream::OpMaker.define_operation :sum do |op|
7
7
  op.what_it_does "If axis has no entries, all dimensions are reduced, and a tensor with a single element is returned."
8
8
 
9
9
  op.parameter :input_a, "tensor X"
10
- op.parameter :axis, "tensor X", :nil, validate: 'INTEGER_TYPES'
10
+ op.parameter :axis_p, "tensor X", :nil, validate: 'INTEGER_TYPES'
11
11
 
12
+ op.option :axis, "axis", :nil, exclude: true
12
13
  op.option :name, "Optional name", :nil
13
14
  op.option :keepdims, "If true, retains reduced dimensions with length 1.", :false
14
15
 
15
16
  op.add_custom "input_a = TensorStream.convert_to_tensor(input_a)"
16
17
  op.add_custom "return input_a if input_a.shape.scalar?"
17
- op.add_custom "axis = cast_axis(input_a, axis)"
18
+ op.add_custom "axis_p = axis_p || axis"
19
+ op.add_custom "axis_p = cast_axis(input_a, axis_p)"
18
20
 
19
21
  op.define_gradient do |grad, node, params|
20
22
  x, y = params
@@ -0,0 +1,23 @@
1
+ TensorStream::OpMaker.define_operation :top_k do |op|
2
+ op.what_it_does "Finds values and indices of the `k` largest entries for the last dimension."
3
+
4
+ op.parameter :input, "1-D or higher `Tensor` with last dimension at least `k`."
5
+ op.parameter :k, "0-D `int32` `Tensor`. Number of top elements to look for along the last dimension (along each row for matrices)", 1
6
+ op.option :sorted, "If true the resulting `k` elements will be sorted by the values in descending order.", "true"
7
+ op.option :name, "Optional name", :nil
8
+
9
+ op.add_custom_post "[result[0], result[1]]"
10
+
11
+ op.define_shape do |tensor|
12
+ next nil unless tensor.inputs[0].shape.known?
13
+
14
+ input_shape = tensor.inputs[0].shape.shape.dup
15
+ k = tensor.options[:k]
16
+ input_shape[-1] = k
17
+ input_shape
18
+ end
19
+
20
+ op.define_gradient do |grad, node, params|
21
+ #TODO
22
+ end
23
+ end
@@ -98,6 +98,9 @@ module TensorStream
98
98
  end
99
99
 
100
100
  def close
101
+ # unlink resources to save memory
102
+ @last_session_context = nil
103
+ @session_cache = {}
101
104
  @closed = true
102
105
  end
103
106
 
@@ -18,7 +18,8 @@ module TensorStream
18
18
  end
19
19
 
20
20
  def [](index)
21
- @shape[index]
21
+ new_shape = @shape[index]
22
+ TensorShape.new(@shape[index])
22
23
  end
23
24
 
24
25
  def ndims
@@ -42,6 +43,36 @@ module TensorStream
42
43
  known?
43
44
  end
44
45
 
46
+ def merge_with(other)
47
+ assert_compatible_with(other)
48
+
49
+ if @shape.nil?
50
+ TensorShape.new(other)
51
+ else
52
+ TensorShape.new(@shape)
53
+ end
54
+ end
55
+
56
+ def compatible_with?(other)
57
+ other = as_dimension(other)
58
+
59
+ shape.nil? || other.nil? || shape == other
60
+ end
61
+
62
+ def as_dimension(value)
63
+ value.is_a?(TensorShape) ? value.shape : value
64
+ end
65
+
66
+ def value
67
+ shape
68
+ end
69
+
70
+ ##
71
+ # Raises an exception if `other` is not compatible with this shape.
72
+ def assert_compatible_with(other)
73
+ raise TensorStream::ValueError, "Dimensions #{self} and #{other} are not compatible" unless compatible_with?(other)
74
+ end
75
+
45
76
  def self.infer_shape(shape_a, shape_b)
46
77
  return nil if shape_a.nil? || shape_b.nil?
47
78
  return shape_a if shape_b.empty?
@@ -7,9 +7,9 @@ module TensorStream
7
7
  class Saver
8
8
  include TensorStream::OpHelper
9
9
 
10
- def initialize
10
+ def initialize(var_list = nil)
11
11
  graph = TensorStream::Graph.get_default_graph
12
- vars = graph.get_collection(GraphKeys::GLOBAL_VARIABLES)
12
+ vars = var_list || graph.get_collection(GraphKeys::GLOBAL_VARIABLES)
13
13
 
14
14
  @filename = graph["ts_filename"] || TensorStream.placeholder(:string, name: "ts_filename", shape: [])
15
15
 
@@ -219,6 +219,10 @@ module TensorStream
219
219
  TensorStream::Trainer
220
220
  end
221
221
 
222
+ def math
223
+ TensorStream::Maths
224
+ end
225
+
222
226
  def image
223
227
  TensorStream::Images
224
228
  end
@@ -248,6 +252,10 @@ module TensorStream
248
252
  return TensorStream.expand_dims(value[0], 0)
249
253
  end
250
254
 
255
+ if value.is_a?(TensorShape)
256
+ value = value.shape
257
+ end
258
+
251
259
  check_if_dense(value)
252
260
  i_cons(value, dtype: dtype || Tensor.detect_type(value), name: name)
253
261
  end
@@ -0,0 +1,11 @@
1
+ module TensorStream
2
+ module PyPorts
3
+ def floor_div(a, b)
4
+ if (a.is_a?(Float))
5
+ (a.to_i / b.to_i).to_f
6
+ else
7
+ a / b
8
+ end
9
+ end
10
+ end
11
+ end
@@ -1,5 +1,5 @@
1
1
  module TensorStream
2
- VERSION = "1.0.6".freeze
2
+ VERSION = "1.0.7".freeze
3
3
 
4
4
  def self.version
5
5
  VERSION
@@ -0,0 +1,192 @@
1
+ #
2
+ # A ruby port of https://github.com/guillaume-chevalier/GloVe-as-a-TensorFlow-Embedding-Layer by Guillaume Chevalier
3
+ #
4
+ # This is a port so some weird python like conventions may have been left behind
5
+ require "bundler/setup"
6
+ require "tensor_stream"
7
+ require "chakin-rb/chakin"
8
+ # require 'pry-byebug'
9
+ require 'zip'
10
+
11
+ tf = TensorStream
12
+
13
+ CHAKIN_INDEX = 17
14
+ NUMBER_OF_DIMENSIONS = 25
15
+ SUBFOLDER_NAME = "glove.twitter.27B"
16
+
17
+ DATA_FOLDER = "embeddings"
18
+ ZIP_FILE = File.join(DATA_FOLDER, "#{SUBFOLDER_NAME}.zip")
19
+ ZIP_FILE_ALT = "glove" + ZIP_FILE[5..nil] # sometimes it's lowercase only...
20
+ UNZIP_FOLDER = File.join(DATA_FOLDER, SUBFOLDER_NAME)
21
+
22
+ if SUBFOLDER_NAME[-1] == "d"
23
+ GLOVE_FILENAME = File.join(UNZIP_FOLDER, "#{SUBFOLDER_NAME}.txt")
24
+ else
25
+ GLOVE_FILENAME = File.join(UNZIP_FOLDER, "#{SUBFOLDER_NAME}.#{NUMBER_OF_DIMENSIONS}d.txt")
26
+ end
27
+
28
+ if !File.exist?(ZIP_FILE) && !File.exist?(UNZIP_FOLDER)
29
+ # GloVe by Stanford is licensed Apache 2.0:
30
+ # https://github.com/stanfordnlp/GloVe/blob/master/LICENSE
31
+ # http://nlp.stanford.edu/data/glove.twitter.27B.zip
32
+ # Copyright 2014 The Board of Trustees of The Leland Stanford Junior University
33
+ puts "Downloading embeddings to '#{ZIP_FILE}'"
34
+ Chakin::Vectors.download(number: CHAKIN_INDEX, save_dir: "./#{DATA_FOLDER}")
35
+ else
36
+ puts "Embeddings already downloaded."
37
+ end
38
+
39
+ if !File.exists?(UNZIP_FOLDER)
40
+ if !File.exists?(ZIP_FILE) && !File.exists?(ZIP_FILE_ALT)
41
+ ZIP_FILE = ZIP_FILE_ALT
42
+ end
43
+ FileUtils.mkdir_p(UNZIP_FOLDER)
44
+ Zip::File.open(ZIP_FILE) do |zipfile|
45
+ zipfile.each do |file|
46
+ puts "Extracting embeddings to '#{UNZIP_FOLDER}/#{file.name}'"
47
+ fpath = File.join(UNZIP_FOLDER, file.name)
48
+ zipfile.extract(file, fpath) unless File.exist?(fpath)
49
+ end
50
+ end
51
+ else
52
+ puts "Embeddings already extracted."
53
+ end
54
+
55
+ ##
56
+ # Read a GloVe txt file. If `with_indexes=True`, we return a tuple of two dictionnaries
57
+ # `(word_to_index_dict, index_to_embedding_array)`, otherwise we return only a direct
58
+ # `word_to_embedding_dict` dictionnary mapping from a string to a numpy array.
59
+ def load_embedding_from_disks(glove_filename, with_indexes: true)
60
+ word_to_index_dict = {}
61
+ index_to_embedding_array = []
62
+ word_to_embedding_dict = {}
63
+ representation = nil
64
+
65
+ last_index = nil
66
+ File.open(glove_filename, 'r').each_with_index do |line, i|
67
+ split = line.split(' ')
68
+
69
+ word = split.shift
70
+
71
+ representation = split
72
+ representation.map! { |val| val.to_f }
73
+
74
+ if with_indexes
75
+ word_to_index_dict[word] = i
76
+ index_to_embedding_array << representation
77
+ else
78
+ word_to_embedding_dict[word] = representation
79
+ end
80
+ last_index = i
81
+ end
82
+
83
+ _WORD_NOT_FOUND = [0.0] * representation.size # Empty representation for unknown words.
84
+ if with_indexes
85
+ _LAST_INDEX = last_index + 1
86
+ word_to_index_dict = Hash.new(_LAST_INDEX).merge(word_to_index_dict)
87
+ index_to_embedding_array = index_to_embedding_array + [_WORD_NOT_FOUND]
88
+ return word_to_index_dict, index_to_embedding_array
89
+ else
90
+ word_to_embedding_dict = Hash.new(_WORD_NOT_FOUND)
91
+ return word_to_embedding_dict
92
+ end
93
+ end
94
+
95
+ puts "Loading embedding from disks..."
96
+ word_to_index, index_to_embedding = load_embedding_from_disks(GLOVE_FILENAME, with_indexes: true)
97
+ puts "Embedding loaded from disks."
98
+
99
+ vocab_size, embedding_dim = index_to_embedding.shape
100
+ puts "Embedding is of shape: #{index_to_embedding.shape}"
101
+ puts "This means (number of words, number of dimensions per word)"
102
+ puts "The first words are words that tend occur more often."
103
+
104
+ puts "Note: for unknown words, the representation is an empty vector,\n" +
105
+ "and the index is the last one. The dictionnary has a limit:"
106
+ puts " \"A word\" --> \"Index in embedding\" --> \"Representation\""
107
+ word = "worsdfkljsdf"
108
+ idx = word_to_index[word]
109
+ embd = index_to_embedding[idx].map { |v| v.to_i } # "int" for compact print only.
110
+ puts " #{word} --> #{idx} --> #{embd}"
111
+ word = "the"
112
+ idx = word_to_index[word]
113
+ embd = index_to_embedding[idx] # "int" for compact print only.
114
+ puts " #{word} --> #{idx} --> #{embd}"
115
+
116
+ words = [
117
+ "The", "Teh", "A", "It", "Its", "Bacon", "Star", "Clone", "Bonjour", "Intelligence",
118
+ "À", "A", "Ça", "Ca", "Été", "C'est", "Aujourd'hui", "Aujourd", "'", "hui", "?", "!", ",", ".", "-", "/", "~"
119
+ ]
120
+
121
+ words.each do |word|
122
+ word_ = word.downcase
123
+ embedding = index_to_embedding[word_to_index[word_]]
124
+ norm = Vector::elements(embedding).norm
125
+ puts (word + ": ").ljust(15) + norm.to_s
126
+ end
127
+
128
+ puts "Note: here we printed words starting with capital letters, \n" +
129
+ "however to take their embeddings we need their lowercase version (str.downcase)"
130
+
131
+ batch_size = nil # Any size is accepted
132
+
133
+ tf.reset_default_graph
134
+ sess = tf.session
135
+
136
+ # Define the variable that will hold the embedding:
137
+ tf_embedding = tf.variable(
138
+ tf.constant(0.0, shape: index_to_embedding.shape),
139
+ trainable: false,
140
+ name: "Embedding"
141
+ )
142
+
143
+ tf_word_ids = tf.placeholder(:int32, shape: [batch_size])
144
+
145
+ tf_word_representation_layer = tf.nn.embedding_lookup(tf_embedding, tf_word_ids)
146
+
147
+ tf_embedding_placeholder = tf.placeholder(:float32, shape: index_to_embedding.shape)
148
+ tf_embedding_init = tf_embedding.assign(tf_embedding_placeholder)
149
+
150
+ sess.run(
151
+ tf_embedding_init,
152
+ feed_dict: {
153
+ tf_embedding_placeholder => index_to_embedding
154
+ }
155
+ )
156
+
157
+ puts "Embedding now stored in TensorStream. Can delete ruby array to clear some CPU RAM."
158
+
159
+ batch_of_words = ["Hello", "World", "!"]
160
+ batch_indexes = batch_of_words.map { |w| word_to_index[w.downcase] }
161
+
162
+ embedding_from_batch_lookup = sess.run(
163
+ tf_word_representation_layer,
164
+ feed_dict: {
165
+ tf_word_ids => batch_indexes
166
+ }
167
+ )
168
+
169
+ puts "Representations for #{batch_of_words}:"
170
+ puts embedding_from_batch_lookup.inspect
171
+
172
+ prefix = SUBFOLDER_NAME + "." + NUMBER_OF_DIMENSIONS.to_s + "d"
173
+ TF_EMBEDDINGS_FILE_NAME = File.join(DATA_FOLDER, prefix + ".ckpt")
174
+ DICT_WORD_TO_INDEX_FILE_NAME = File.join(DATA_FOLDER, prefix + ".json")
175
+
176
+ variables_to_save = [tf_embedding]
177
+ embedding_saver = tf::Train::Saver.new(variables_to_save)
178
+ embedding_saver.save(sess, TF_EMBEDDINGS_FILE_NAME)
179
+ puts "TF embeddings saved to '#{TF_EMBEDDINGS_FILE_NAME}'."
180
+
181
+ sess.close
182
+
183
+ File.open(DICT_WORD_TO_INDEX_FILE_NAME, 'w') do |f|
184
+ f.write(word_to_index.to_json)
185
+ end
186
+ puts "word_to_index dict saved to '#{DICT_WORD_TO_INDEX_FILE_NAME}'."
187
+
188
+ words_B = "like absolutely crazy not hate bag sand rock soap"
189
+ r = words_B.split.map { |w| word_to_index[w.strip()] }
190
+ puts words_B
191
+ puts r.inspect
192
+ puts "done"