הצטרף ל-TensorFlow ב-Google I/O, 11-12 במאי הירשם עכשיו

יצירת מספרים אקראיים

הצג באתר TensorFlow.org הפעל בגוגל קולאב צפה במקור ב-GitHub הורד מחברת

TensorFlow מספקת קבוצה של מחוללי מספרים פסאודו-אקראיים (RNG), במודול tf.random . מסמך זה מתאר כיצד ניתן לשלוט במחוללי המספרים האקראיים, וכיצד המחוללים הללו מקיימים אינטראקציה עם תת-מערכות זרימת טנסור אחרות.

TensorFlow מספקת שתי גישות לשליטה בתהליך יצירת המספרים האקראיים:

  1. באמצעות שימוש מפורש באובייקטי tf.random.Generator . כל אובייקט כזה שומר על מצב (ב- tf.Variable ) שישתנה לאחר כל יצירת מספרים.

  2. באמצעות הפונקציות האקראיות חסרות המצב הפונקציונליות כמו tf.random.stateless_uniform . קריאה לפונקציות הללו עם אותם ארגומנטים (הכוללים את ה-seed) ובאותו מכשיר תמיד תניב את אותן תוצאות.

להכין

import tensorflow as tf

# Creates some virtual devices (cpu:0, cpu:1, etc.) for using distribution strategy
physical_devices = tf.config.list_physical_devices("CPU")
tf.config.experimental.set_virtual_device_configuration(
    physical_devices[0], [
        tf.config.experimental.VirtualDeviceConfiguration(),
        tf.config.experimental.VirtualDeviceConfiguration(),
        tf.config.experimental.VirtualDeviceConfiguration()
    ])

הכיתה tf.random.Generator

המחלקה tf.random.Generator משמשת במקרים שבהם אתה רוצה שכל קריאת RNG תפיק תוצאות שונות. הוא שומר על מצב פנימי (מנוהל על ידי אובייקט tf.Variable ) אשר יתעדכן בכל פעם שנוצרים מספרים אקראיים. מכיוון שהמדינה מנוהלת על ידי tf.Variable , היא נהנית מכל המתקנים המסופקים על ידי tf.Variable כגון נקודת ביקורת קלה, תלות בקרה אוטומטית ובטיחות חוטים.

אתה יכול לקבל tf.random.Generator על ידי יצירה ידנית של אובייקט של המחלקה או לקרוא tf.random.get_global_generator() כדי לקבל את המחולל הגלובלי המוגדר כברירת מחדל:

g1 = tf.random.Generator.from_seed(1)
print(g1.normal(shape=[2, 3]))
g2 = tf.random.get_global_generator()
print(g2.normal(shape=[2, 3]))
tf.Tensor(
[[ 0.43842277 -0.53439844 -0.07710262]
 [ 1.5658046  -0.1012345  -0.2744976 ]], shape=(2, 3), dtype=float32)
tf.Tensor(
[[-0.5496427   0.24263908 -1.1436267 ]
 [ 1.861458   -0.6756685  -0.9900103 ]], shape=(2, 3), dtype=float32)

ישנן מספר דרכים ליצור אובייקט מחולל. הקל ביותר הוא Generator.from_seed , כפי שמוצג לעיל, שיוצר מחולל מזרע. זרע הוא כל מספר שלם לא שלילי. from_seed גם לוקח ארגומנט אופציונלי alg שהוא אלגוריתם RNG שישמש את המחולל הזה:

g1 = tf.random.Generator.from_seed(1, alg='philox')
print(g1.normal(shape=[2, 3]))
tf.Tensor(
[[ 0.43842277 -0.53439844 -0.07710262]
 [ 1.5658046  -0.1012345  -0.2744976 ]], shape=(2, 3), dtype=float32)

עיין בסעיף האלגוריתמים למטה למידע נוסף עליו.

דרך נוספת ליצור מחולל היא באמצעות Generator.from_non_deterministic_state . מחולל שנוצר כך יתחיל ממצב לא דטרמיניסטי, תלוי למשל בזמן ובמערכת ההפעלה.

g = tf.random.Generator.from_non_deterministic_state()
print(g.normal(shape=[2, 3]))
tf.Tensor(
[[-0.9078738   0.11009752  1.037219  ]
 [ 0.661036    0.4169741   1.4539026 ]], shape=(2, 3), dtype=float32)

ישנן דרכים נוספות ליצור גנרטורים, כגון ממצבים מפורשים, שאינן מכוסות במדריך זה.

בעת שימוש ב- tf.random.get_global_generator כדי לקבל את המחולל הגלובלי, עליך להיות זהיר לגבי מיקום המכשיר. המחולל הגלובלי נוצר (ממצב לא דטרמיניסטי) בפעם הראשונה שנקרא tf.random.get_global_generator , וממוקם בהתקן ברירת המחדל באותה קריאה. כך, למשל, אם האתר הראשון שאתה קורא tf.random.get_global_generator נמצא בטווח tf.device("gpu") , המחולל הגלובלי ימוקם על ה-GPU, ושימוש במחולל הגלובלי מאוחר יותר מה-CPU לקבל עותק GPU-to-CPU.

קיימת גם פונקציה tf.random.set_global_generator להחלפת המחולל הגלובלי באובייקט מחולל אחר. עם זאת, יש להשתמש בפונקציה זו בזהירות, מכיוון שייתכן שהמחולל הגלובלי הישן נתפס על ידי tf.function .( כהתייחסות חלשה), והחלפתו תגרום לאיסוף אשפה, לשבור את tf.function . דרך טובה יותר לאפס את המחולל הגלובלי היא להשתמש באחת מפונקציות ה"איפוס" כגון Generator.reset_from_seed , אשר לא תיצור אובייקטים מחוללים חדשים.

g = tf.random.Generator.from_seed(1)
print(g.normal([]))
print(g.normal([]))
g.reset_from_seed(1)
print(g.normal([]))
tf.Tensor(0.43842277, shape=(), dtype=float32)
tf.Tensor(1.6272374, shape=(), dtype=float32)
tf.Tensor(0.43842277, shape=(), dtype=float32)

יצירת זרמי מספר אקראי עצמאי

ביישומים רבים יש צורך במספר זרמים בלתי תלויים של מספרים אקראיים, בלתי תלויים במובן זה שהם לא יחפפו ולא יהיו להם מתאמים הניתנים לזיהוי סטטיסטית. זה מושג על ידי שימוש ב- Generator.split ליצירת גנרטורים מרובים שמובטחים שהם בלתי תלויים זה בזה (כלומר יצירת זרמים עצמאיים).

g = tf.random.Generator.from_seed(1)
print(g.normal([]))
new_gs = g.split(3)
for new_g in new_gs:
  print(new_g.normal([]))
print(g.normal([]))
tf.Tensor(0.43842277, shape=(), dtype=float32)
tf.Tensor(2.536413, shape=(), dtype=float32)
tf.Tensor(0.33186463, shape=(), dtype=float32)
tf.Tensor(-0.07144657, shape=(), dtype=float32)
tf.Tensor(-0.79253083, shape=(), dtype=float32)

split ישנה את מצב המחולל שבו הוא נקרא ( g בדוגמה לעיל), בדומה לשיטת RNG כגון normal . בנוסף להיותם בלתי תלויים זה בזה, מובטח שהמחוללים החדשים ( new_gs ) יהיו גם בלתי תלויים מהישן ( g ).

יצירת גנרטורים חדשים שימושית גם כאשר אתה רוצה לוודא שהמחולל שבו אתה משתמש נמצא באותו התקן כמו חישובים אחרים, כדי למנוע את התקורה של העתקה חוצת-מכשירים. לדוגמה:

with tf.device("cpu"):  # change "cpu" to the device you want
  g = tf.random.get_global_generator().split(1)[0]  
  print(g.normal([]))  # use of g won't cause cross-device copy, unlike the global generator
tf.Tensor(0.4142675, shape=(), dtype=float32)

אתה יכול לעשות פיצול רקורסיבי, לקרוא split במחוללים מפוצלים. אין מגבלות (ללא הצפת מספרים שלמים) על עומק הרקורסיות.

אינטראקציה עם tf.function

tf.random.Generator מציית לאותם כללים כמו tf.Variable כאשר משתמשים בו עם tf.function . זה כולל שלושה היבטים.

יצירת גנרטורים מחוץ tf.function

tf.function יכול להשתמש במחולל שנוצר מחוצה לו.

g = tf.random.Generator.from_seed(1)
@tf.function
def foo():
  return g.normal([])
print(foo())
tf.Tensor(0.43842277, shape=(), dtype=float32)

המשתמש צריך לוודא שאובייקט המחולל עדיין חי (לא נאסף אשפה) כאשר הפונקציה נקראת.

יצירת גנרטורים בתוך tf.function

יצירת גנרטורים בתוך פונקציה tf.function . יכולה להתרחש רק במהלך הריצה הראשונה של הפונקציה.

g = None
@tf.function
def foo():
  global g
  if g is None:
    g = tf.random.Generator.from_seed(1)
  return g.normal([])
print(foo())
print(foo())
tf.Tensor(0.43842277, shape=(), dtype=float32)
tf.Tensor(1.6272374, shape=(), dtype=float32)

העברת גנרטורים כארגומנטים ל- tf.function

כאשר משתמשים בהם כארגומנט ל- tf.function , אובייקטים מחוללים שונים יגרמו לחזרה של ה- tf.function .

num_traces = 0
@tf.function
def foo(g):
  global num_traces
  num_traces += 1
  return g.normal([])
foo(tf.random.Generator.from_seed(1))
foo(tf.random.Generator.from_seed(2))
print(num_traces)
2

שים לב שהתנהגות העקיבה הזו תואמת ל- tf.Variable :

num_traces = 0
@tf.function
def foo(v):
  global num_traces
  num_traces += 1
  return v.read_value()
foo(tf.Variable(1))
foo(tf.Variable(2))
print(num_traces)
2

אינטראקציה עם אסטרטגיות הפצה

ישנן שתי דרכים בהן Generator מקיים אינטראקציה עם אסטרטגיות הפצה.

יצירת גנרטורים מחוץ לאסטרטגיות הפצה

אם נוצר מחולל מחוץ להיקפי האסטרטגיה, הגישה של כל העתקים למחולל תבוצע בסידרה, ומכאן שהעותקים יקבלו מספרים אקראיים שונים.

g = tf.random.Generator.from_seed(1)
strat = tf.distribute.MirroredStrategy(devices=["cpu:0", "cpu:1"])
with strat.scope():
  def f():
    print(g.normal([]))
  results = strat.run(f)
WARNING:tensorflow:There are non-GPU devices in `tf.distribute.Strategy`, not using nccl allreduce.
INFO:tensorflow:Using MirroredStrategy with devices ('/job:localhost/replica:0/task:0/device:CPU:0', '/job:localhost/replica:0/task:0/device:CPU:1')
WARNING:tensorflow:Using MirroredStrategy eagerly has significant overhead currently. We will be working on improving this in the future, but for now please wrap `call_for_each_replica` or `experimental_run` or `run` inside a tf.function to get the best performance.
tf.Tensor(0.43842274, shape=(), dtype=float32)
tf.Tensor(1.6272374, shape=(), dtype=float32)

שימו לב שלשימוש זה עשויות להיות בעיות ביצועים מכיוון שהמכשיר של המחולל שונה מהעותקים העתקים.

יצירת גנרטורים בתוך אסטרטגיות הפצה

אם נוצר מחולל בתוך היקף אסטרטגיה, כל העתק יקבל זרם שונה ובלתי תלוי של מספרים אקראיים.

strat = tf.distribute.MirroredStrategy(devices=["cpu:0", "cpu:1"])
with strat.scope():
  g = tf.random.Generator.from_seed(1)
  print(strat.run(lambda: g.normal([])))
  print(strat.run(lambda: g.normal([])))
WARNING:tensorflow:There are non-GPU devices in `tf.distribute.Strategy`, not using nccl allreduce.
INFO:tensorflow:Using MirroredStrategy with devices ('/job:localhost/replica:0/task:0/device:CPU:0', '/job:localhost/replica:0/task:0/device:CPU:1')
WARNING:tensorflow:Using MirroredStrategy eagerly has significant overhead currently. We will be working on improving this in the future, but for now please wrap `call_for_each_replica` or `experimental_run` or `run` inside a tf.function to get the best performance.
PerReplica:{
  0: tf.Tensor(-0.87930447, shape=(), dtype=float32),
  1: tf.Tensor(0.020661574, shape=(), dtype=float32)
}
WARNING:tensorflow:Using MirroredStrategy eagerly has significant overhead currently. We will be working on improving this in the future, but for now please wrap `call_for_each_replica` or `experimental_run` or `run` inside a tf.function to get the best performance.
PerReplica:{
  0: tf.Tensor(-1.5822568, shape=(), dtype=float32),
  1: tf.Tensor(0.77539235, shape=(), dtype=float32)
}

אם המחולל זרע (למשל נוצר על ידי Generator.from_seed ), המספרים האקראיים נקבעים על ידי הזרע, למרות שהעתקים שונים מקבלים מספרים שונים ולא מתואמים. אפשר לחשוב על מספר אקראי שנוצר על רפליקה כ-hash של מזהה העתק ומספר אקראי "ראשי" המשותף לכל ההעתקים. לפיכך, המערכת כולה עדיין דטרמיניסטית.

ניתן ליצור tf.random.Generator גם בתוך Strategy.run :

strat = tf.distribute.MirroredStrategy(devices=["cpu:0", "cpu:1"])
with strat.scope():
  def f():
    g = tf.random.Generator.from_seed(1)
    a = g.normal([])
    b = g.normal([])
    return tf.stack([a, b])
  print(strat.run(f))
  print(strat.run(f))
WARNING:tensorflow:There are non-GPU devices in `tf.distribute.Strategy`, not using nccl allreduce.
INFO:tensorflow:Using MirroredStrategy with devices ('/job:localhost/replica:0/task:0/device:CPU:0', '/job:localhost/replica:0/task:0/device:CPU:1')
WARNING:tensorflow:Using MirroredStrategy eagerly has significant overhead currently. We will be working on improving this in the future, but for now please wrap `call_for_each_replica` or `experimental_run` or `run` inside a tf.function to get the best performance.
PerReplica:{
  0: tf.Tensor([-0.87930447 -1.5822568 ], shape=(2,), dtype=float32),
  1: tf.Tensor([0.02066157 0.77539235], shape=(2,), dtype=float32)
}
WARNING:tensorflow:Using MirroredStrategy eagerly has significant overhead currently. We will be working on improving this in the future, but for now please wrap `call_for_each_replica` or `experimental_run` or `run` inside a tf.function to get the best performance.
PerReplica:{
  0: tf.Tensor([-0.87930447 -1.5822568 ], shape=(2,), dtype=float32),
  1: tf.Tensor([0.02066157 0.77539235], shape=(2,), dtype=float32)
}

אנחנו כבר לא ממליצים להעביר tf.random.Generator כארגומנטים ל- Strategy.run , מכיוון ש- Strategy.run מצפה בדרך כלל שהארגומנטים יהיו טנזורים, לא מחוללים.

שמירת גנרטורים

בדרך כלל עבור שמירה או סדרה, אתה יכול לטפל ב- tf.random.Generator באותו אופן שבו היית מטפל ב- tf.Variable או tf.Module (או תת המחלקות שלו). ב-TF ישנם שני מנגנונים לסריאליזציה: Checkpoint ו- SavedModel .

מחסום

ניתן לשמור ולשחזר גנרטורים באופן חופשי באמצעות tf.train.Checkpoint . זרם המספרים האקראיים מנקודת השחזור יהיה זהה לזה מנקודת השמירה.

filename = "./checkpoint"
g = tf.random.Generator.from_seed(1)
cp = tf.train.Checkpoint(generator=g)
print(g.normal([]))
tf.Tensor(0.43842277, shape=(), dtype=float32)
cp.write(filename)
print("RNG stream from saving point:")
print(g.normal([]))
print(g.normal([]))
RNG stream from saving point:
tf.Tensor(1.6272374, shape=(), dtype=float32)
tf.Tensor(1.6307176, shape=(), dtype=float32)
cp.restore(filename)
print("RNG stream from restoring point:")
print(g.normal([]))
print(g.normal([]))
RNG stream from restoring point:
tf.Tensor(1.6272374, shape=(), dtype=float32)
tf.Tensor(1.6307176, shape=(), dtype=float32)

אתה יכול גם לשמור ולשחזר בתוך אסטרטגיית הפצה:

filename = "./checkpoint"
strat = tf.distribute.MirroredStrategy(devices=["cpu:0", "cpu:1"])
with strat.scope():
  g = tf.random.Generator.from_seed(1)
  cp = tf.train.Checkpoint(my_generator=g)
  print(strat.run(lambda: g.normal([])))
WARNING:tensorflow:There are non-GPU devices in `tf.distribute.Strategy`, not using nccl allreduce.
INFO:tensorflow:Using MirroredStrategy with devices ('/job:localhost/replica:0/task:0/device:CPU:0', '/job:localhost/replica:0/task:0/device:CPU:1')
PerReplica:{
  0: tf.Tensor(-0.87930447, shape=(), dtype=float32),
  1: tf.Tensor(0.020661574, shape=(), dtype=float32)
}
with strat.scope():
  cp.write(filename)
  print("RNG stream from saving point:")
  print(strat.run(lambda: g.normal([])))
  print(strat.run(lambda: g.normal([])))
RNG stream from saving point:
PerReplica:{
  0: tf.Tensor(-1.5822568, shape=(), dtype=float32),
  1: tf.Tensor(0.77539235, shape=(), dtype=float32)
}
PerReplica:{
  0: tf.Tensor(-0.5039703, shape=(), dtype=float32),
  1: tf.Tensor(0.1251838, shape=(), dtype=float32)
}
with strat.scope():
  cp.restore(filename)
  print("RNG stream from restoring point:")
  print(strat.run(lambda: g.normal([])))
  print(strat.run(lambda: g.normal([])))
RNG stream from restoring point:
PerReplica:{
  0: tf.Tensor(-1.5822568, shape=(), dtype=float32),
  1: tf.Tensor(0.77539235, shape=(), dtype=float32)
}
PerReplica:{
  0: tf.Tensor(-0.5039703, shape=(), dtype=float32),
  1: tf.Tensor(0.1251838, shape=(), dtype=float32)
}

עליך לוודא שהעותקים העתקים אינם מתפצלים בהיסטוריית שיחות ה-RNG שלהם (למשל העתק אחד מבצע קריאת RNG אחת בעוד אחר מבצע שתי שיחות RNG) לפני השמירה. אחרת, מצבי ה-RNG הפנימיים שלהם יתפצלו ו- tf.train.Checkpoint (ששומר רק את המצב של העתק הראשון) לא ישחזר כראוי את כל ההעתקים.

ניתן גם לשחזר מחסום שמור לאסטרטגיית הפצה אחרת עם מספר שונה של העתקים. מכיוון שאובייקט tf.random.Generator שנוצר באסטרטגיה יכול לשמש רק באותה אסטרטגיה, כדי לשחזר לאסטרטגיה אחרת, יש ליצור tf.random.Generator חדש באסטרטגיית היעד ו- tf.train.Checkpoint עבורו, כפי שמוצג בדוגמה זו:

filename = "./checkpoint"
strat1 = tf.distribute.MirroredStrategy(devices=["cpu:0", "cpu:1"])
with strat1.scope():
  g1 = tf.random.Generator.from_seed(1)
  cp1 = tf.train.Checkpoint(my_generator=g1)
  print(strat1.run(lambda: g1.normal([])))
WARNING:tensorflow:There are non-GPU devices in `tf.distribute.Strategy`, not using nccl allreduce.
INFO:tensorflow:Using MirroredStrategy with devices ('/job:localhost/replica:0/task:0/device:CPU:0', '/job:localhost/replica:0/task:0/device:CPU:1')
PerReplica:{
  0: tf.Tensor(-0.87930447, shape=(), dtype=float32),
  1: tf.Tensor(0.020661574, shape=(), dtype=float32)
}
with strat1.scope():
  cp1.write(filename)
  print("RNG stream from saving point:")
  print(strat1.run(lambda: g1.normal([])))
  print(strat1.run(lambda: g1.normal([])))
RNG stream from saving point:
PerReplica:{
  0: tf.Tensor(-1.5822568, shape=(), dtype=float32),
  1: tf.Tensor(0.77539235, shape=(), dtype=float32)
}
PerReplica:{
  0: tf.Tensor(-0.5039703, shape=(), dtype=float32),
  1: tf.Tensor(0.1251838, shape=(), dtype=float32)
}
strat2 = tf.distribute.MirroredStrategy(devices=["cpu:0", "cpu:1", "cpu:2"])
with strat2.scope():
  g2 = tf.random.Generator.from_seed(1)
  cp2 = tf.train.Checkpoint(my_generator=g2)
  cp2.restore(filename)
  print("RNG stream from restoring point:")
  print(strat2.run(lambda: g2.normal([])))
  print(strat2.run(lambda: g2.normal([])))
WARNING:tensorflow:There are non-GPU devices in `tf.distribute.Strategy`, not using nccl allreduce.
INFO:tensorflow:Using MirroredStrategy with devices ('/job:localhost/replica:0/task:0/device:CPU:0', '/job:localhost/replica:0/task:0/device:CPU:1', '/job:localhost/replica:0/task:0/device:CPU:2')
RNG stream from restoring point:
PerReplica:{
  0: tf.Tensor(-1.5822568, shape=(), dtype=float32),
  1: tf.Tensor(0.77539235, shape=(), dtype=float32),
  2: tf.Tensor(0.6851049, shape=(), dtype=float32)
}
PerReplica:{
  0: tf.Tensor(-0.5039703, shape=(), dtype=float32),
  1: tf.Tensor(0.1251838, shape=(), dtype=float32),
  2: tf.Tensor(-0.58519536, shape=(), dtype=float32)
}

למרות ש- g1 ו- cp1 הם אובייקטים שונים מ- g2 ו- cp2 , הם מקושרים באמצעות שם הקובץ הנפוץ של filename המחסום ושם האובייקט my_generator . העתקים חופפים בין אסטרטגיות (למשל cpu:0 ו- cpu:1 לעיל) ישוחזרו כראוי את זרמי ה-RNG שלהם, כמו בדוגמאות קודמות. ערבות זו אינה מכסה את המקרה שבו מחולל נשמר בהיקף אסטרטגיה ומשוחזר מחוץ לכל היקף אסטרטגיה או להיפך, מכיוון שמכשיר מחוץ לאסטרטגיות מטופל כשונה מכל העתק באסטרטגיה.

SavedModel

ניתן לשמור את tf.random.Generator ב-SaveModel. ניתן ליצור את המחולל במסגרת אסטרטגיה. החיסכון יכול להתרחש גם במסגרת אסטרטגיה.

filename = "./saved_model"

class MyModule(tf.Module):

  def __init__(self):
    super(MyModule, self).__init__()
    self.g = tf.random.Generator.from_seed(0)

  @tf.function
  def __call__(self):
    return self.g.normal([])

  @tf.function
  def state(self):
    return self.g.state

strat = tf.distribute.MirroredStrategy(devices=["cpu:0", "cpu:1"])
with strat.scope():
  m = MyModule()
  print(strat.run(m))
  print("state:", m.state())
WARNING:tensorflow:There are non-GPU devices in `tf.distribute.Strategy`, not using nccl allreduce.
INFO:tensorflow:Using MirroredStrategy with devices ('/job:localhost/replica:0/task:0/device:CPU:0', '/job:localhost/replica:0/task:0/device:CPU:1')
PerReplica:{
  0: tf.Tensor(-1.4154755, shape=(), dtype=float32),
  1: tf.Tensor(-0.113884404, shape=(), dtype=float32)
}
state: tf.Tensor([256   0   0], shape=(3,), dtype=int64)
with strat.scope():
  tf.saved_model.save(m, filename)
  print("RNG stream from saving point:")
  print(strat.run(m))
  print("state:", m.state())
  print(strat.run(m))
  print("state:", m.state())
INFO:tensorflow:Assets written to: ./saved_model/assets
RNG stream from saving point:
PerReplica:{
  0: tf.Tensor(-0.68758255, shape=(), dtype=float32),
  1: tf.Tensor(0.8084062, shape=(), dtype=float32)
}
state: tf.Tensor([512   0   0], shape=(3,), dtype=int64)
PerReplica:{
  0: tf.Tensor(-0.27342677, shape=(), dtype=float32),
  1: tf.Tensor(-0.53093255, shape=(), dtype=float32)
}
state: tf.Tensor([768   0   0], shape=(3,), dtype=int64)
2021-09-22 20:45:46.222281: W tensorflow/python/util/util.cc:348] Sets are not currently considered sequences, but this may change in the future, so consider avoiding using them.
imported = tf.saved_model.load(filename)
print("RNG stream from loading point:")
print("state:", imported.state())
print(imported())
print("state:", imported.state())
print(imported())
print("state:", imported.state())
RNG stream from loading point:
state: tf.Tensor([256   0   0], shape=(3,), dtype=int64)
tf.Tensor(-1.0359411, shape=(), dtype=float32)
state: tf.Tensor([512   0   0], shape=(3,), dtype=int64)
tf.Tensor(-0.06425078, shape=(), dtype=float32)
state: tf.Tensor([768   0   0], shape=(3,), dtype=int64)

טעינת SavedModel המכיל את tf.random.Generator לתוך אסטרטגיית הפצה אינה מומלצת מכיוון שהעותקים יפיקו כולם את אותו זרם של מספר אקראי (וזה בגלל שמזהה העתק הוקפא בגרף של SavedModel).

tf.random.Generator מבוזר (מחולל שנוצר במסגרת אסטרטגיית הפצה) לסביבה שאינה אסטרטגית, כמו הדוגמה לעיל, יש גם אזהרה. מצב ה-RNG ישוחזר כראוי, אבל המספרים האקראיים שנוצרו יהיו שונים מהמחולל המקורי באסטרטגיה שלו (שוב מכיוון שמכשיר מחוץ לאסטרטגיות מטופל כשונה מכל העתק באסטרטגיה).

RNGs חסרי מדינה

השימוש ב-RNGs חסרי מדינה הוא פשוט. מכיוון שהם רק פונקציות טהורות, אין מצב או תופעת לוואי מעורבים.

print(tf.random.stateless_normal(shape=[2, 3], seed=[1, 2]))
print(tf.random.stateless_normal(shape=[2, 3], seed=[1, 2]))
tf.Tensor(
[[ 0.5441101   0.20738031  0.07356433]
 [ 0.04643455 -1.30159    -0.95385665]], shape=(2, 3), dtype=float32)
tf.Tensor(
[[ 0.5441101   0.20738031  0.07356433]
 [ 0.04643455 -1.30159    -0.95385665]], shape=(2, 3), dtype=float32)

כל RNG חסר מדינה דורש ארגומנט seed , שצריך להיות טנסור שלם של צורה [2] . תוצאות הניתוח נקבעות במלואן על ידי זרע זה.

אלגוריתם ה-RNG בשימוש על ידי RNGs חסרי מצב תלוי במכשיר, כלומר אותו הפעלה שפועל במכשיר אחר עשוי להפיק פלטים שונים.

אלגוריתמים

כללי

גם המחלקה tf.random.Generator וגם הפונקציות stateless המצב תומכות באלגוריתם Philox (נכתב כ "philox" או tf.random.Algorithm.PHILOX ) בכל המכשירים.

מכשירים שונים יפיקו את אותם מספרים שלמים, אם משתמשים באותו אלגוריתם ומתחילים מאותו מצב. הם גם יפיקו "כמעט אותם" מספרי נקודה צפה, אם כי ייתכנו פערים מספריים קטנים הנגרמים מהדרכים השונות שבהן מבצעים המכשירים את חישוב הנקודה הצפה (למשל סדר הפחתת).

מכשירי XLA

בהתקנים מונעי XLA (כגון TPU, וגם CPU/GPU כאשר XLA מופעל) נתמך גם אלגוריתם ThreeFry (שכתוב כ- "threefry" או tf.random.Algorithm.THREEFRY ). האלגוריתם הזה מהיר ב-TPU אבל איטי ב-CPU/GPU בהשוואה ל-Philox.

ראה מאמר 'מספרים אקראיים מקבילים: קל כמו 1, 2, 3' לפרטים נוספים על אלגוריתמים אלה.