tensorflow word2vec demo詳解

轉自https://blog.csdn.net/weixin_42001089/article/details/81224869

word2vec有CBOW與Skip-Gram模型

CBOW是根據上下文預測中間值,Skip-Gram則恰恰相反

本文首先介紹Skip-Gram模型,是基於tensorflow官方提供的一個demo,第二大部分是經過簡單修改的CBOW模型,主要參考:

http://www.javashuo.com/article/p-wshmytwc-dt.html

兩部分以###########################爲界限

好了,現在開始!!!!!!

###################################################################################################

tensorflow官方demo:

https://github.com/tensorflow/tensorflow/tree/master/tensorflow/examples/tutorials/word2vec

(一)首先:就是導入一些包沒什麼可說的

  
  
  1. from __future__ import absolute_import
  2. from __future__ import division
  3. from __future__ import print_function
  4. import collections
  5. import math
  6. import os
  7. import sys
  8. import argparse
  9. import random
  10. from tempfile import gettempdir
  11. import zipfile
  12. import numpy as np
  13. from six.moves import urllib
  14. from six.moves import xrange # pylint: disable=redefined-builtin
  15. import tensorflow as tf
  16. from tensorflow.contrib.tensorboard.plugins import projector

(二)接下來就是獲取當前路徑,以及創建log目錄(主要用於後續的tensorboard可視化),默認log目錄在當前目錄下:
 

  
  
  1. current_path = os.path.dirname(os.path.realpath(sys.argv[ 0]))
  2. parser = argparse.ArgumentParser()
  3. parser.add_argument(
  4. '--log_dir',
  5. type=str,
  6. default=os.path.join(current_path, 'log'),
  7. help= 'The log directory for TensorBoard summaries.')
  8. FLAGS, unparsed = parser.parse_known_args()
  9. # Create the directory for TensorBoard variables if there is not.
  10. if not os.path.exists(FLAGS.log_dir):
  11. os.makedirs(FLAGS.log_dir)

sys.argv[]就是一個從程序外部獲取參數的橋樑,sys.argv[0]就是返回第一個參數,即獲取當前腳本

關於其更多用法可以參考:http://www.javashuo.com/article/p-cthlxfyn-n.html

os.path.realpath就是獲取腳本的絕對路徑

parser.parse_known_args()用 來解析不定長的命令行參數,其返回的是2個參數,第一個參數是已經定義了的參數,第二個是沒有定義的參數。

具體到這裏舉個例子就是:寫一個test.py

  
  
  1. import argparse
  2. import os
  3. import sys
  4. current_path = os.path.dirname(os.path.realpath(sys.argv[ 0]))
  5. parser = argparse.ArgumentParser()
  6. parser.add_argument(
  7. '--log_dir',
  8. type=str,
  9. default=os.path.join(current_path, 'log'),
  10. help= 'The log directory for TensorBoard summaries.')
  11. FLAGS, unparsed = parser.parse_known_args()
  12. print(FLAGS)
  13. print(unparsed)

(三)接下來是下載數據集(這裏稍微做了一點修改):

  
  
  1. def maybe_download(filename, expected_bytes):
  2. """Download a file if not present, and make sure it's the right size."""
  3. if not os.path.exists(filename):
  4. filename, _ = urllib.request.urlretrieve(url + filename, filename)
  5. # 獲取文件相關屬性
  6. statinfo = os.stat(filename)
  7. # 比對文件的大小是否正確
  8. if statinfo.st_size == expected_bytes:
  9. print( 'Found and verified', filename)
  10. else:
  11. print(statinfo.st_size)
  12. raise Exception(
  13. 'Failed to verify ' + filename + '. Can you get to it with a browser?')
  14. return filename
  15. filename = maybe_download( 'text8.zip', 31344016)

下載好後就會在當前文件夾下有一個叫做text8.zip的壓縮包

(四)生成單詞表

  
  
  1. # Read the data into a list of strings.
  2. def read_data(filename):
  3. """Extract the first file enclosed in a zip file as a list of words."""
  4. with zipfile.ZipFile(filename) as f:
  5. data = tf.compat.as_str(f.read(f.namelist()[ 0])).split()
  6. return data
  7. vocabulary = read_data(filename)
  8. print( 'Data size', len(vocabulary))

f.namelist()[0]是解壓後第一個文件,不過這裏解壓後本來就只有一個文件,然後以空格分開,所以最後的vocabulary中就是單詞表,最後打印一下看看有多少單詞

(五)建立有50000個詞的字典,沒在該詞典的單詞用UNK表示 

  
  
  1. vocabulary_size = 50000
  2. def build_dataset(words, n_words):
  3. """Process raw inputs into a dataset."""
  4. count = [[ 'UNK', -1]]
  5. count.extend(collections.Counter(words).most_common(n_words - 1))
  6. dictionary = dict()
  7. for word, _ in count:
  8. dictionary[word] = len(dictionary)
  9. data = list()
  10. unk_count = 0
  11. for word in words:
  12. index = dictionary.get(word, 0)
  13. if index == 0: # dictionary['UNK']
  14. unk_count += 1
  15. data.append(index)
  16. count[ 0][ 1] = unk_count
  17. reversed_dictionary = dict(zip(dictionary.values(), dictionary.keys()))
  18. return data, count, dictionary, reversed_dictionary
  19. data, count, dictionary, reverse_dictionary = build_dataset(
  20. vocabulary, vocabulary_size)
  21. del vocabulary # Hint to reduce memory.
  22. print( 'Most common words (+UNK)', count[: 5])
  23. print( 'Sample data', data[: 10], [reverse_dictionary[i] for i in data[: 10]])

其中下面是統計每個單詞的詞頻,並選取前50000個詞頻較高的單詞作爲字典的備選詞

extend追加一個列表

  
  
count.extend(collections.Counter(words).most_common(n_words - 1))

data是將數據集的單詞都編號,沒有在字典的中單詞編號爲UNK(0)

就想這樣;

i    love   tensorflow  very  much .........

2    23      UNK           3       45    .........

count 記錄的是每個單詞對應的詞頻比如;[ ['UNK', -1] , ['a','200'] , ['i',150],...............]

dictionary是一個字典:記錄的是單詞對應編號 即key:單詞、value:編號(編號越小,詞頻越高,但第一個永遠是UNK)

reversed_dictionary是一個字典:編號對應的單詞  即key:編號、value:單詞(編號越小,詞頻越高,但第一個永遠是UNK)

第一個永遠是UNK是因爲extend追加一個列表,變化的是追加的列表,第一個永遠是UNK

(六)取labels,分批次

  
  
  1. data_index = 0
  2. def generate_batch(batch_size, num_skips, skip_window):
  3. global data_index
  4. assert batch_size % num_skips == 0
  5. assert num_skips <= 2 * skip_window
  6. batch = np.ndarray(shape=(batch_size), dtype=np.int32)
  7. labels = np.ndarray(shape=(batch_size, 1), dtype=np.int32)
  8. span = 2 * skip_window + 1 # [ skip_window target skip_window ]
  9. buffer = collections.deque(maxlen=span) # pylint: disable=redefined-builtin
  10. if data_index + span > len(data):
  11. data_index = 0
  12. buffer.extend(data[data_index:data_index + span])
  13. data_index += span
  14. for i in range(batch_size // num_skips):
  15. context_words = [w for w in range(span) if w != skip_window]
  16. words_to_use = random.sample(context_words, num_skips)
  17. for j, context_word in enumerate(words_to_use):
  18. batch[i * num_skips + j] = buffer[skip_window]
  19. labels[i * num_skips + j, 0] = buffer[context_word]
  20. if data_index == len(data):
  21. buffer.extend(data[ 0:span])
  22. data_index = span
  23. else:
  24. buffer.append(data[data_index])
  25. data_index += 1
  26. # Backtrack a little bit to avoid skipping words in the end of a batch
  27. data_index = (data_index + len(data) - span) % len(data)
  28. return batch, labels
  29. batch, labels = generate_batch(batch_size= 8, num_skips= 2, skip_window= 1)
  30. for i in range( 8):
  31. print(batch[i], reverse_dictionary[batch[i]], '->', labels[i, 0],
  32. reverse_dictionary[labels[i, 0]])

batch_size:就是批次大小

 num_skips:就是重複用一個單詞的次數,比如 num_skips=2時,對於一句話:i    love   tensorflow  very  much ..........

                        當tensorflow被選爲目標詞時,在產生label時要利用tensorflow兩次即:

                         tensorflow---》 love        tensorflow---》 very

skip_window:是考慮左右上下文的個數,比如skip_window=1,就是在考慮上下文的時候,左面一個,右面一個

                           skip_window=2時,就是在考慮上下文的時候,左面兩個,右面兩個

 span :其實在分批次的過程中可以看做是一個固定大小的框框(比較流行的說法數滑動窗口)在不斷移動,而這個框框的大小                  就是 span,可以看到span = 2 * skip_window + 1 

 buffer = collections.deque(maxlen=span):就是申請了一個buffer(其實就是固定大小的窗口這裏是3)即每次這個buffer隊列中最                                                                           多 能容納span個單詞

 

所以過程應該是這樣的:比如batch_size=6, num_skips=2,skip_window=1,data:

batch_size // num_skips=3,循環3次

(   I      am      looking     for     the     missing     glass-shoes     who     has     picked   it      up .............)

    2      23        56            3       45         84               123              45        23           12     1     14 ...............

i=0時:2 ,23 ,56首先進入 buffer( context_words = [w for w in range(span) if w != skip_window]的意思就是取窗口中不包括目標詞              的詞即上下文),然後batch[i * num_skips + j] = buffer[skip_window](skip_window=1,所以每次就是取窗口的中間數爲                目標詞)即batch=23,  labels[i * num_skips + j, 0] = buffer[context_word]就是取其上下文爲labels即2和56

             所以此時batch=[23,23] labels=[2,56](當然也可能是[2,56],因爲可能先取右邊,後取左面),同時data_index=3即單詞for的              位置

i=1時:data[data_index]進隊列,即 buffer爲 23,56,3 賦值後爲:batch=[23,23,56,56] labels=[2,56,23,3](也可能是換一下順序)

             同時data_index=4即單詞the

i=2時:data[data_index]進隊列,即 buffer爲 56,3,45 賦值後爲:batch=[23,23,56,56,3,3]  labels=[2,56,23,3,56,45](也可能是換一              下順序) 同時data_index=5即單詞missing

至此循環結束,按要求取出大小爲6的一個批次即:

                                                batch=[23,23,56,56,3,3]                                labels=[2,56,23,3,56,45]

 然後data_index = (data_index + len(data) - span) % len(data)即data_index回溯3個單位,回到 looking,因爲global data_index

所以data_index全局變量,所以當在取下一個批次的時候,buffer從looking的位置開始裝載,即從上一個批次結束的位置接着往下取batch和labels

(七)定義一些參數大小:

  
  
  1. batch_size = 128
  2. embedding_size = 128 # Dimension of the embedding vector.
  3. skip_window = 1 # How many words to consider left and right.
  4. num_skips = 2 # How many times to reuse an input to generate a label.
  5. num_sampled = 64 # Number of negative examples to sample.
  6. graph = tf.Graph()

這裏主要就是定義我們上面講的一些參數的大小

(八)神經網絡圖model:

  
  
  1. with graph.as_default():
  2. # Input data.
  3. with tf.name_scope( 'inputs'):
  4. train_inputs = tf.placeholder(tf.int32, shape=[batch_size])
  5. train_labels = tf.placeholder(tf.int32, shape=[batch_size, 1])
  6. valid_dataset = tf.constant(valid_examples, dtype=tf.int32)
  7. # Ops and variables pinned to the CPU because of missing GPU implementation
  8. with tf.device( '/cpu:0'):
  9. # Look up embeddings for inputs.
  10. with tf.name_scope( 'embeddings'):
  11. embeddings = tf.Variable(
  12. tf.random_uniform([vocabulary_size, embedding_size], -1.0, 1.0))
  13. embed = tf.nn.embedding_lookup(embeddings, train_inputs)
  14. # Construct the variables for the NCE loss
  15. with tf.name_scope( 'weights'):
  16. nce_weights = tf.Variable(
  17. tf.truncated_normal(
  18. [vocabulary_size, embedding_size],
  19. stddev= 1.0 / math.sqrt(embedding_size)))
  20. with tf.name_scope( 'biases'):
  21. nce_biases = tf.Variable(tf.zeros([vocabulary_size]))
  22. # Compute the average NCE loss for the batch.
  23. # tf.nce_loss automatically draws a new sample of the negative labels each
  24. # time we evaluate the loss.
  25. # Explanation of the meaning of NCE loss:
  26. # http://mccormickml.com/2016/04/19/word2vec-tutorial-the-skip-gram-model/
  27. with tf.name_scope( 'loss'):
  28. loss = tf.reduce_mean(
  29. tf.nn.nce_loss(
  30. weights=nce_weights,
  31. biases=nce_biases,
  32. labels=train_labels,
  33. inputs=embed,
  34. num_sampled=num_sampled,
  35. num_classes=vocabulary_size))
  36. # Add the loss value as a scalar to summary.
  37. tf.summary.scalar( 'loss', loss)
  38. # Construct the SGD optimizer using a learning rate of 1.0.
  39. with tf.name_scope( 'optimizer'):
  40. optimizer = tf.train.GradientDescentOptimizer( 1.0).minimize(loss)
  41. # Compute the cosine similarity between minibatch examples and all embeddings.
  42. norm = tf.sqrt(tf.reduce_sum(tf.square(embeddings), 1, keepdims= True))
  43. normalized_embeddings = embeddings / norm
  44. valid_embeddings = tf.nn.embedding_lookup(normalized_embeddings,
  45. valid_dataset)
  46. similarity = tf.matmul(
  47. valid_embeddings, normalized_embeddings, transpose_b= True)
  48. # Merge all summaries.
  49. merged = tf.summary.merge_all()
  50. # Add variable initializer.
  51. init = tf.global_variables_initializer()
  52. # Create a saver.
  53. saver = tf.train.Saver()

這裏可以分爲兩部分來看,一部分是訓練Skip-gram模型的詞向量,另一部分是計算餘弦相似度,下面我們分開說:

首先看下tf.nn.embedding_lookup的API解釋:

https://github.com/tensorflow/tensorflow/blob/master/tensorflow/python/ops/embedding_ops.py

  
  
  1. def embedding_lookup(
  2. params,
  3. ids,
  4. partition_strategy="mod",
  5. name=None,
  6. validate_indices=True, # pylint: disable=unused-argument
  7. max_norm=None):
  8. """Looks up `ids` in a list of embedding tensors.
  9. This function is used to perform parallel lookups on the list of
  10. tensors in `params`. It is a generalization of
  11. @{tf.gather}, where ` params` is
  12. interpreted as a partitioning of a large embedding tensor. ` params` may be
  13. a `PartitionedVariable` as returned by using `tf.get_variable()` with a
  14. partitioner.
  15. If `len( params) > 1`, each element `id` of `ids` is partitioned between
  16. the elements of ` params` according to the `partition_strategy`.
  17. In all strategies, if the id space does not evenly divide the number of
  18. partitions, each of the first `(max_id + 1) % len( params)` partitions will
  19. be assigned one more id.
  20. If `partition_strategy` is ` "mod"`, we assign each id to partition
  21. `p = id % len( params)`. For instance,
  22. 13 ids are split across 5 partitions as:
  23. `[[ 0, 5, 10], [ 1, 6, 11], [ 2, 7, 12], [ 3, 8], [ 4, 9]]`
  24. If `partition_strategy` is ` "div"`, we assign ids to partitions in a
  25. contiguous manner. In this case, 13 ids are split across 5 partitions as:
  26. `[[ 0, 1, 2], [ 3, 4, 5], [ 6, 7, 8], [ 9, 10], [ 11, 12]]`
  27. The results of the lookup are concatenated into a dense
  28. tensor. The returned tensor has shape `shape(ids) + shape( params)[ 1:]`.

看到 The results of the lookup are concatenated into a dense tensor. The returned tensor has shape `shape(ids) + shape(params)[1:]`.,即假如params是:100*28,sp_ids是[2,56,3] 那麼返回的便是3*28即分別對應params的第3、57、4行

其實往下看會發現其主要調用的是 _embedding_lookup_and_transform函數

-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

來重點看下tf.nn.nce_loss源碼(這也是本demo中最核心的東西):

源碼https://github.com/tensorflow/tensorflow/blob/master/tensorflow/python/ops/nn_impl.py

  
  
  1. def nce_loss(weights,
  2. biases,
  3. labels,
  4. inputs,
  5. num_sampled,
  6. num_classes,
  7. num_true=1,
  8. sampled_values=None,
  9. remove_accidental_hits=False,
  10. partition_strategy="mod",
  11. name="nce_loss"):
  12. """Computes and returns the noise-contrastive estimation training loss.
  13. See [Noise-contrastive estimation: A new estimation principle for
  14. unnormalized statistical
  15. models](http://www.jmlr.org/proceedings/papers/v9/gutmann10a/gutmann10a.pdf).
  16. Also see our [Candidate Sampling Algorithms
  17. Reference](https://www.tensorflow.org/extras/candidate_sampling.pdf)
  18. A common use case is to use this method for training, and calculate the full
  19. sigmoid loss for evaluation or inference. In this case, you must set
  20. `partition_strategy="div"` for the two losses to be consistent, as in the
  21. following example:
  22. `` `python
  23. if mode == "train":
  24. loss = tf.nn.nce_loss(
  25. weights=weights,
  26. biases=biases,
  27. labels=labels,
  28. inputs=inputs,
  29. ...,
  30. partition_strategy="div")
  31. elif mode == "eval":
  32. logits = tf.matmul(inputs, tf.transpose(weights))
  33. logits = tf.nn.bias_add(logits, biases)
  34. labels_one_hot = tf.one_hot(labels, n_classes)
  35. loss = tf.nn.sigmoid_cross_entropy_with_logits(
  36. labels=labels_one_hot,
  37. logits=logits)
  38. loss = tf.reduce_sum(loss, axis=1)
  39. ` ``
  40. Note: By default this uses a log- uniform (Zipfian) distribution for sampling,
  41. so your labels must be sorted in order of decreasing frequency to achieve
  42. good results. For more details, see
  43. @{tf.nn.log_uniform_candidate_sampler}.
  44. Note: In the case where `num_true` > 1, we assign to each target class
  45. the target probability 1 / `num_true` so that the target probabilities
  46. sum to 1 per-example.
  47. Note: It would be useful to allow a variable number of target classes per
  48. example. We hope to provide this functionality in a future release.
  49. For now, if you have a variable number of target classes, you can pad them
  50. out to a constant number by either repeating them or by padding
  51. with an otherwise unused class.
  52. Args:
  53. weights: A `Tensor` of shape `[num_classes, dim]`, or a list of `Tensor`
  54. objects whose concatenation along dimension 0 has shape
  55. [num_classes, dim]. The (possibly-partitioned) class embeddings.
  56. biases: A `Tensor` of shape `[num_classes]`. The class biases.
  57. labels: A `Tensor` of type `int64` and shape `[batch_size,
  58. num_true]`. The target classes.
  59. inputs: A `Tensor` of shape `[batch_size, dim]`. The forward
  60. activations of the input network.
  61. num_sampled: An `int`. The number of classes to randomly sample per batch.
  62. num_classes: An `int`. The number of possible classes.
  63. num_true: An `int`. The number of target classes per training example.
  64. sampled_values: a tuple of ( `sampled_candidates`, `true_expected_count`,
  65. `sampled_expected_count`) returned by a `*_candidate_sampler` function.
  66. ( if None, we default to `log_uniform_candidate_sampler`)
  67. remove_accidental_hits: A `bool`. Whether to remove "accidental hits"
  68. where a sampled class equals one of the target classes. If set to
  69. `True`, this is a "Sampled Logistic" loss instead of NCE, and we are
  70. learning to generate log-odds instead of log probabilities. See
  71. our [Candidate Sampling Algorithms Reference]
  72. (https://www.tensorflow.org/extras/candidate_sampling.pdf).
  73. Default is False.
  74. partition_strategy: A string specifying the partitioning strategy, relevant
  75. if `len(weights) > 1`. Currently `"div"` and `"mod"` are supported.
  76. Default is `"mod"`. See `tf.nn.embedding_lookup` for more details.
  77. name: A name for the operation (optional).
  78. Returns:
  79. A `batch_size` 1-D tensor of per-example NCE losses.
  80. """
  81. logits, labels = _compute_sampled_logits(
  82. weights=weights,
  83. biases=biases,
  84. labels=labels,
  85. inputs=inputs,
  86. num_sampled=num_sampled,
  87. num_classes=num_classes,
  88. num_true=num_true,
  89. sampled_values=sampled_values,
  90. subtract_log_q=True,
  91. remove_accidental_hits=remove_accidental_hits,
  92. partition_strategy=partition_strategy,
  93. name=name)
  94. sampled_losses = sigmoid_cross_entropy_with_logits(
  95. labels=labels, logits=logits, name="sampled_losses ")
  96. # sampled_losses is batch_size x {true_loss, sampled_losses...}
  97. # We sum out true and sampled losses.
  98. return _sum_rows(sampled_losses)

首先來看一下API:

  
  
  1. def nce_loss(weights,
  2.              biases,
  3.              labels,
  4.              inputs,
  5.              num_sampled,
  6.              num_classes,
  7.              num_true=1,
  8.              sampled_values=None,
  9.              remove_accidental_hits=False,
  10.              partition_strategy="mod",
  11.              name="nce_loss"):

假如現在輸入數據是M*N(對應到我們這個demo就是說M=50000(詞典單詞數),N=128(word2vec的特徵數))

那麼:

weights:M*N

biases   :    N

labels    :   batch_size, num_true(num_true代表正樣本的數量,本demo中爲1)

inputs    :   batch_size *N

num_sampled: 採樣的負樣本

num_classes : M

sampled_values:是否用不同的採樣器,即tuple(`sampled_candidates`, `true_expected_count`  `sampled_expected_count`)

                              如果是None,這採用log_uniform_candidate_sampler

remove_accidental_hits:如果不下心採集到的負樣本就是target,要不要捨棄

partition_strategy:並行策略問題。

再看一下返回的就是

一個batch_size內每一個類子的NCE losses

下面看一下其實現,主要由三部分構成:

_compute_sampled_logits-----------------------採樣

sigmoid_cross_entropy_with_logits---------------------------logistic regression

_sum_rows------------------------------------------------------------求和。

(1)看一下_compute_sampled_logits

  
  
  1. def _compute_sampled_logits(weights,
  2. biases,
  3. labels,
  4. inputs,
  5. num_sampled,
  6. num_classes,
  7. num_true=1,
  8. sampled_values=None,
  9. subtract_log_q=True,
  10. remove_accidental_hits=False,
  11. partition_strategy="mod",
  12. name=None,
  13. seed=None):
  14. """Helper function for nce_loss and sampled_softmax_loss functions.
  15. Computes sampled output training logits and labels suitable for implementing
  16. e.g. noise-contrastive estimation (see nce_loss) or sampled softmax (see
  17. sampled_softmax_loss).
  18. Note: In the case where num_true > 1, we assign to each target class
  19. the target probability 1 / num_true so that the target probabilities
  20. sum to 1 per-example.
  21. Args:
  22. weights: A `Tensor` of shape `[num_classes, dim]`, or a list of `Tensor`
  23. objects whose concatenation along dimension 0 has shape
  24. `[num_classes, dim]`. The (possibly-partitioned) class embeddings.
  25. biases: A `Tensor` of shape `[num_classes]`. The (possibly-partitioned)
  26. class biases.
  27. labels: A `Tensor` of type `int64` and shape `[batch_size,
  28. num_true]`. The target classes. Note that this format differs from
  29. the `labels` argument of `nn.softmax_cross_entropy_with_logits_v2`.
  30. inputs: A `Tensor` of shape `[batch_size, dim]`. The forward
  31. activations of the input network.
  32. num_sampled: An `int`. The number of classes to randomly sample per batch.
  33. num_classes: An `int`. The number of possible classes.
  34. num_true: An `int`. The number of target classes per training example.
  35. sampled_values: a tuple of (`sampled_candidates`, `true_expected_count`,
  36. `sampled_expected_count`) returned by a `*_candidate_sampler` function.
  37. (if None, we default to `log_uniform_candidate_sampler`)
  38. subtract_log_q: A `bool`. whether to subtract the log expected count of
  39. the labels in the sample to get the logits of the true labels.
  40. Default is True. Turn off for Negative Sampling.
  41. remove_accidental_hits: A `bool`. whether to remove "accidental hits"
  42. where a sampled class equals one of the target classes. Default is
  43. False.
  44. partition_strategy: A string specifying the partitioning strategy, relevant
  45. if `len(weights) > 1`. Currently `"div"` and `"mod"` are supported.
  46. Default is `"mod"`. See `tf.nn.embedding_lookup` for more details.
  47. name: A name for the operation (optional).
  48. seed: random seed for candidate sampling. Default to None, which doesn't set
  49. the op-level random seed for candidate sampling.
  50. Returns:
  51. out_logits: `Tensor` object with shape
  52. `[batch_size, num_true + num_sampled]`, for passing to either
  53. `nn.sigmoid_cross_entropy_with_logits` (NCE) or
  54. `nn.softmax_cross_entropy_with_logits_v2` (sampled softmax).
  55. out_labels: A Tensor object with the same shape as `out_logits`.
  56. """
  57. if isinstance(weights, variables.PartitionedVariable):
  58. weights = list(weights)
  59. if not isinstance(weights, list):
  60. weights = [weights]
  61. with ops.name_scope(name, "compute_sampled_logits",
  62. weights + [biases, inputs, labels]):
  63. if labels.dtype != dtypes.int64:
  64. labels = math_ops.cast(labels, dtypes.int64)
  65. labels_flat = array_ops.reshape(labels, [ -1])
  66. # Sample the negative labels.
  67. # sampled shape: [num_sampled] tensor
  68. # true_expected_count shape = [batch_size, 1] tensor
  69. # sampled_expected_count shape = [num_sampled] tensor
  70. if sampled_values is None:
  71. sampled_values = candidate_sampling_ops.log_uniform_candidate_sampler(
  72. true_classes=labels,
  73. num_true=num_true,
  74. num_sampled=num_sampled,
  75. unique= True,
  76. range_max=num_classes,
  77. seed=seed)
  78. # NOTE: pylint cannot tell that 'sampled_values' is a sequence
  79. # pylint: disable=unpacking-non-sequence
  80. sampled, true_expected_count, sampled_expected_count = (
  81. array_ops.stop_gradient(s) for s in sampled_values)
  82. # pylint: enable=unpacking-non-sequence
  83. sampled = math_ops.cast(sampled, dtypes.int64)
  84. # labels_flat is a [batch_size * num_true] tensor
  85. # sampled is a [num_sampled] int tensor
  86. all_ids = array_ops.concat([labels_flat, sampled], 0)
  87. # Retrieve the true weights and the logits of the sampled weights.
  88. # weights shape is [num_classes, dim]
  89. all_w = embedding_ops.embedding_lookup(
  90. weights, all_ids, partition_strategy=partition_strategy)
  91. # true_w shape is [batch_size * num_true, dim]
  92. true_w = array_ops.slice(all_w, [ 0, 0],
  93. array_ops.stack(
  94. [array_ops.shape(labels_flat)[ 0], -1]))
  95. sampled_w = array_ops.slice(
  96. all_w, array_ops.stack([array_ops.shape(labels_flat)[ 0], 0]), [ -1, -1])
  97. # inputs has shape [batch_size, dim]
  98. # sampled_w has shape [num_sampled, dim]
  99. # Apply X*W', which yields [batch_size, num_sampled]
  100. sampled_logits = math_ops.matmul(inputs, sampled_w, transpose_b= True)
  101. # Retrieve the true and sampled biases, compute the true logits, and
  102. # add the biases to the true and sampled logits.
  103. all_b = embedding_ops.embedding_lookup(
  104. biases, all_ids, partition_strategy=partition_strategy)
  105. # true_b is a [batch_size * num_true] tensor
  106. # sampled_b is a [num_sampled] float tensor
  107. true_b = array_ops.slice(all_b, [ 0], array_ops.shape(labels_flat))
  108. sampled_b = array_ops.slice(all_b, array_ops.shape(labels_flat), [ -1])
  109. # inputs shape is [batch_size, dim]
  110. # true_w shape is [batch_size * num_true, dim]
  111. # row_wise_dots is [batch_size, num_true, dim]
  112. dim = array_ops.shape(true_w)[ 1: 2]
  113. new_true_w_shape = array_ops.concat([[ -1, num_true], dim], 0)
  114. row_wise_dots = math_ops.multiply(
  115. array_ops.expand_dims(inputs, 1),
  116. array_ops.reshape(true_w, new_true_w_shape))
  117. # We want the row-wise dot plus biases which yields a
  118. # [batch_size, num_true] tensor of true_logits.
  119. dots_as_matrix = array_ops.reshape(row_wise_dots,
  120. array_ops.concat([[ -1], dim], 0))
  121. true_logits = array_ops.reshape(_sum_rows(dots_as_matrix), [ -1, num_true])
  122. true_b = array_ops.reshape(true_b, [ -1, num_true])
  123. true_logits += true_b
  124. sampled_logits += sampled_b
  125. if remove_accidental_hits:
  126. acc_hits = candidate_sampling_ops.compute_accidental_hits(
  127. labels, sampled, num_true=num_true)
  128. acc_indices, acc_ids, acc_weights = acc_hits
  129. # This is how SparseToDense expects the indices.
  130. acc_indices_2d = array_ops.reshape(acc_indices, [ -1, 1])
  131. acc_ids_2d_int32 = array_ops.reshape(
  132. math_ops.cast(acc_ids, dtypes.int32), [ -1, 1])
  133. sparse_indices = array_ops.concat([acc_indices_2d, acc_ids_2d_int32], 1,
  134. "sparse_indices")
  135. # Create sampled_logits_shape = [batch_size, num_sampled]
  136. sampled_logits_shape = array_ops.concat(
  137. [array_ops.shape(labels)[: 1],
  138. array_ops.expand_dims(num_sampled, 0)], 0)
  139. if sampled_logits.dtype != acc_weights.dtype:
  140. acc_weights = math_ops.cast(acc_weights, sampled_logits.dtype)
  141. sampled_logits += sparse_ops.sparse_to_dense(
  142. sparse_indices,
  143. sampled_logits_shape,
  144. acc_weights,
  145. default_value= 0.0,
  146. validate_indices= False)
  147. if subtract_log_q:
  148. # Subtract log of Q(l), prior probability that l appears in sampled.
  149. true_logits -= math_ops.log(true_expected_count)
  150. sampled_logits -= math_ops.log(sampled_expected_count)
  151. # Construct output logits and labels. The true labels/logits start at col 0.
  152. out_logits = array_ops.concat([true_logits, sampled_logits], 1)
  153. # true_logits is a float tensor, ones_like(true_logits) is a float
  154. # tensor of ones. We then divide by num_true to ensure the per-example
  155. # labels sum to 1.0, i.e. form a proper probability distribution.
  156. out_labels = array_ops.concat([
  157. array_ops.ones_like(true_logits) / num_true,
  158. array_ops.zeros_like(sampled_logits)
  159. ], 1)
  160. return out_logits, out_labels

首先看一下開頭註解的返回維數:

  
  
  1. Returns:
  2. out_logits: `Tensor` object with shape
  3. `[batch_size, num_true + num_sampled]`, for passing to either
  4. `nn.sigmoid_cross_entropy_with_logits` (NCE) or
  5. `nn.softmax_cross_entropy_with_logits_v2` (sampled softmax).
  6. out_labels: A Tensor object with the same shape as `out_logits`.

即 返回的out_logits和 out_labels的維度都是[batch_size, num_true + num_sampled],其中 num_true + num_sampled代表的就是正樣本數+負樣本數

再看一下最後:

  
  
  1. out_labels = array_ops.concat([
  2. array_ops.ones_like(true_logits) / num_true,
  3. array_ops.zeros_like(sampled_logits)
  4. ], 1)

其中的array_ops.ones_like和array_ops.zeros_like就是賦值向量爲全1和全0,也可以從下面的源碼看到:

https://github.com/tensorflow/tensorflow/blob/master/tensorflow/python/ops/array_ops.py

  
  
  1. @tf_export("ones_like")
  2. def ones_like(tensor, dtype=None, name=None, optimize=True):
  3. """Creates a tensor with all elements set to 1.
  4. Given a single tensor (`tensor`), this operation returns a tensor of the same
  5. type and shape as `tensor` with all elements set to 1. Optionally, you can
  6. specify a new type (`dtype`) for the returned tensor.
  7. For example:
  8. ```python
  9. tensor = tf.constant([[1, 2, 3], [4, 5, 6]])
  10. tf.ones_like(tensor) # [[1, 1, 1], [1, 1, 1]]
  11. ```
  12. Args:
  13. tensor: A `Tensor`.
  14. dtype: A type for the returned `Tensor`. Must be `float32`, `float64`,
  15. `int8`, `uint8`, `int16`, `uint16`, `int32`, `int64`,
  16. `complex64`, `complex128` or `bool`.
  17. name: A name for the operation (optional).
  18. optimize: if true, attempt to statically determine the shape of 'tensor'
  19. and encode it as a constant.
  20. Returns:
  21. A `Tensor` with all elements set to 1.
  22. """
  23. with ops.name_scope(name, "ones_like", [tensor]) as name:
  24. tensor = ops.convert_to_tensor(tensor, name= "tensor")
  25. ones_shape = shape_internal(tensor, optimize=optimize)
  26. if dtype is None:
  27. dtype = tensor.dtype
  28. ret = ones(ones_shape, dtype=dtype, name=name)
  29. if not context.executing_eagerly():
  30. ret.set_shape(tensor.get_shape())
  31. return ret

所以總結一下就是:

out_logits返回的就是目標詞彙

out_labels返回的就是正樣本+負樣本(其中正樣本都標記爲1,負樣本都標記爲0)

這也是負採樣的精髓所在,因爲結果只有兩種結果,所以只做二分類就可以了,代替了之前需要預測整個詞典的大小,比如要對本類子中的50000種結果的每一種都預測,所以減少了計算的複雜度!!!!!!!!!!!!!!!

二者的維度都是[batch_size, num_true + num_sampled]

同時因爲該demo中sampled_values=None,所以

  
  
  1. if sampled_values is None:
  2. sampled_values = candidate_sampling_ops.log_uniform_candidate_sampler(
  3. true_classes=labels,
  4. num_true=num_true,
  5. num_sampled=num_sampled,
  6. unique= True,
  7. range_max=num_classes,
  8. seed=seed)

用到的是:candidate_sampling_ops.log_uniform_candidate_sampler採樣器

https://github.com/tensorflow/tensorflow/blob/master/tensorflow/python/ops/candidate_sampling_ops.py

  
  
  1. def log_uniform_candidate_sampler(true_classes, num_true, num_sampled, unique,
  2. range_max, seed=None, name=None):
  3. """Samples a set of classes using a log- uniform (Zipfian) base distribution.
  4. This operation randomly samples a tensor of sampled classes
  5. ( `sampled_candidates`) from the range of integers `[0, range_max)`.
  6. The elements of `sampled_candidates` are drawn without replacement
  7. ( if `unique=True`) or with replacement ( if `unique=False`) from
  8. the base distribution.
  9. The base distribution for this operation is an approximately log- uniform
  10. or Zipfian distribution:
  11. `P(class) = (log(class + 2) - log(class + 1)) / log(range_max + 1)`
  12. This sampler is useful when the target classes approximately follow such
  13. a distribution - for example, if the classes represent words in a lexicon
  14. sorted in decreasing order of frequency. If your classes are not ordered by
  15. decreasing frequency, do not use this op.
  16. In addition, this operation returns tensors `true_expected_count`
  17. and `sampled_expected_count` representing the number of times each
  18. of the target classes ( `true_classes`) and the sampled
  19. classes ( `sampled_candidates`) is expected to occur in an average
  20. tensor of sampled classes. These values correspond to `Q(y|x)`
  21. defined in [this
  22. document]( http://www.tensorflow.org/extras/candidate_sampling.pdf).
  23. If `unique=True`, then these are post-rejection probabilities and we
  24. compute them approximately.
  25. Args:
  26. true_classes: A `Tensor` of type `int64` and shape `[batch_size,
  27. num_true]`. The target classes.
  28. num_true: An `int`. The number of target classes per training example.
  29. num_sampled: An `int`. The number of classes to randomly sample.
  30. unique: A `bool`. Determines whether all sampled classes in a batch are
  31. unique.
  32. range_max: An `int`. The number of possible classes.
  33. seed: An `int`. An operation-specific seed. Default is 0.
  34. name: A name for the operation (optional).
  35. Returns:
  36. sampled_candidates: A tensor of type `int64` and shape `[num_sampled]`.
  37. The sampled classes.
  38. true_expected_count: A tensor of type `float`. Same shape as
  39. `true_classes`. The expected counts under the sampling distribution
  40. of each of `true_classes`.
  41. sampled_expected_count: A tensor of type `float`. Same shape as
  42. `sampled_candidates`. The expected counts under the sampling distribution
  43. of each of `sampled_candidates`.
  44. """
  45. seed1, seed2 = random_seed.get_seed(seed)
  46. return gen_candidate_sampling_ops.log_uniform_candidate_sampler(
  47. true_classes, num_true, num_sampled, unique, range_max, seed=seed1,
  48. seed2=seed2, name=name)

可以看到其對負樣本是基於以下概率採樣的,之所以不使用詞頻直接作爲概率採用是因爲如果這樣的話,那麼採取的負樣本就都會是哪些高頻詞彙類如:and , of , i 等等,顯然並不好。另一個極端就是使用詞頻的倒數,但是這對英文也沒有代表性,根據mikolov寫的一篇論文,實驗得出的經驗值是p(w)=\frac{f(w)^{\frac{3}{4}}}{\sum_{i=1}^{50000}f(w)\frac{3}{4}}

這裏的話沒有用上面的公式,但是也使得其處於兩個極端之間了:還是可以看出P(class) 是遞減函數,即class越小,P(class)越大,class在本類中代表的是單詞的編號,由(五)可以知道,詞頻越大,編號越小(NUK除外),所以詞頻高的還是容易被作採用作爲負樣本的!

P(class) = (log(class + 2) - log(class + 1)) / log(range_max + 1)

(2)接下來看一下sigmoid_cross_entropy_with_logits函數

  
  
  1. def sigmoid_cross_entropy_with_logits( # pylint: disable=invalid-name
  2. _sentinel=None,
  3. labels=None,
  4. logits=None,
  5. name=None):
  6. """Computes sigmoid cross entropy given `logits`.
  7. Measures the probability error in discrete classification tasks in which each
  8. class is independent and not mutually exclusive. For instance, one could
  9. perform multilabel classification where a picture can contain both an elephant
  10. and a dog at the same time.
  11. For brevity, let `x = logits`, `z = labels`. The logistic loss is
  12. z * -log(sigmoid(x)) + (1 - z) * -log(1 - sigmoid(x))
  13. = z * -log(1 / (1 + exp(-x))) + (1 - z) * -log(exp(-x) / (1 + exp(-x)))
  14. = z * log(1 + exp(-x)) + (1 - z) * (-log(exp(-x)) + log(1 + exp(-x)))
  15. = z * log(1 + exp(-x)) + (1 - z) * (x + log(1 + exp(-x))
  16. = (1 - z) * x + log(1 + exp(-x))
  17. = x - x * z + log(1 + exp(-x))
  18. For x < 0, to avoid overflow in exp(-x), we reformulate the above
  19. x - x * z + log(1 + exp(-x))
  20. = log(exp(x)) - x * z + log(1 + exp(-x))
  21. = - x * z + log(1 + exp(x))
  22. Hence, to ensure stability and avoid overflow, the implementation uses this
  23. equivalent formulation
  24. max(x, 0) - x * z + log(1 + exp(-abs(x)))
  25. `logits` and `labels` must have the same type and shape.
  26. Args:
  27. _sentinel: Used to prevent positional parameters. Internal, do not use.
  28. labels: A `Tensor` of the same type and shape as `logits`.
  29. logits: A `Tensor` of type `float32` or `float64`.
  30. name: A name for the operation (optional).
  31. Returns:
  32. A `Tensor` of the same shape as `logits` with the componentwise
  33. logistic losses.
  34. Raises:
  35. ValueError: If `logits` and `labels` do not have the same shape.
  36. """
  37. # pylint: disable=protected-access
  38. nn_ops._ensure_xent_args( "sigmoid_cross_entropy_with_logits", _sentinel,
  39. labels, logits)
  40. # pylint: enable=protected-access
  41. with ops.name_scope(name, "logistic_loss", [logits, labels]) as name:
  42. logits = ops.convert_to_tensor(logits, name= "logits")
  43. labels = ops.convert_to_tensor(labels, name= "labels")
  44. try:
  45. labels.get_shape().merge_with(logits.get_shape())
  46. except ValueError:
  47. raise ValueError( "logits and labels must have the same shape (%s vs %s)" %
  48. (logits.get_shape(), labels.get_shape()))
  49. # The logistic loss formula from above is
  50. # x - x * z + log(1 + exp(-x))
  51. # For x < 0, a more numerically stable formula is
  52. # -x * z + log(1 + exp(x))
  53. # Note that these two expressions can be combined into the following:
  54. # max(x, 0) - x * z + log(1 + exp(-abs(x)))
  55. # To allow computing gradients at zero, we define custom versions of max and
  56. # abs functions.
  57. zeros = array_ops.zeros_like(logits, dtype=logits.dtype)
  58. cond = (logits >= zeros)
  59. relu_logits = array_ops.where(cond, logits, zeros)
  60. neg_abs_logits = array_ops.where(cond, -logits, logits)
  61. return math_ops.add(
  62. relu_logits - logits * labels,
  63. labels: A `Tensor` of the same type and shape as `logits`.
  64. logits: A `Tensor` of type `float32` or `float64`.
  65. name: A name for the operation (optional).
  66. Returns:
  67. A `Tensor` of the same shape as `logits` with the componentwise
  68. logistic losses.
  69. Raises:
  70. ValueError: If `logits` and `labels` do not have the same shape.
  71. """
  72. # pylint: disable=protected-access
  73. nn_ops._ensure_xent_args( "sigmoid_cross_entropy_with_logits", _sentinel,
  74. labels, logits)
  75. # pylint: enable=protected-access
  76. with ops.name_scope(name, "logistic_loss", [logits, labels]) as name:
  77. logits = ops.convert_to_tensor(logits, name= "logits")
  78. labels = ops.convert_to_tensor(labels, name= "labels")
  79. try:
  80. labels.get_shape().merge_with(logits.get_shape())
  81. except ValueError:
  82. raise ValueError( "logits and labels must have the same shape (%s vs %s)" %
  83. (logits.get_shape(), labels.get_shape()))
  84. # The logistic loss formula from above is
  85. # x - x * z + log(1 + exp(-x))
  86. # For x < 0, a more numerically stable formula is
  87. # -x * z + log(1 + exp(x))
  88. # Note that these two expressions can be combined into the following:
  89. # max(x, 0) - x * z + log(1 + exp(-abs(x)))
  90. # To allow computing gradients at zero, we define custom versions of max and
  91. # abs functions.
  92. zeros = array_ops.zeros_like(logits, dtype=logits.dtype)
  93. cond = (logits >= zeros)
  94. relu_logits = array_ops.where(cond, logits, zeros)
  95. neg_abs_logits = array_ops.where(cond, -logits, logits)
  96. return math_ops.add(
  97. relu_logits - logits * labels,
  98. math_ops.log1p(math_ops.exp(neg_abs_logits)),
  99. name=name)

可以看到其實最關鍵的就是下面這個公式:

z * -log(sigmoid(x)) + (1 - z) * -log(1 - sigmoid(x))

其實z * -log(x) + (1 - z) * -log(1 - x)就是交叉熵,對的,沒看錯這個函數其實就是將輸入先sigmoid再計算交叉熵

如上所示最後化簡結果爲:x - x * z + log(1 + exp(-x))

這裏考慮到當x<0時exp(-x)有可能溢出

相關文章
相關標籤/搜索