العمل مع الموترات المتفرقة

عرض على TensorFlow.org تشغيل في Google Colab عرض على جيثب تحميل دفتر

عند العمل مع الموترات التي تحتوي على الكثير من القيم الصفرية ، من المهم تخزينها بطريقة فعالة من حيث المساحة والوقت. تتيح الموترات المتفرقة التخزين الفعال ومعالجة الموترات التي تحتوي على الكثير من القيم الصفرية. تُستخدم الموترات المتفرقة على نطاق واسع في مخططات التشفير مثل TF-IDF كجزء من المعالجة المسبقة للبيانات في تطبيقات البرمجة اللغوية العصبية ومعالجة الصور مسبقًا مع الكثير من وحدات البكسل المظلمة في تطبيقات رؤية الكمبيوتر.

موترات متفرقة في TensorFlow

يمثل TensorFlow موترات متفرقة من خلال كائن tf.SparseTensor . حاليًا ، يتم ترميز الموترات المتفرقة في TensorFlow باستخدام تنسيق قائمة الإحداثيات (COO). تم تحسين تنسيق الترميز هذا للمصفوفات شديدة التباين مثل حفلات الزفاف.

يتكون ترميز COO للموترات المتفرقة من:

  • values : موتر 1D مع شكل [N] يحتوي على جميع القيم غير الصفرية.
  • indices : موتر ثنائي الأبعاد ذو شكل [N, rank] ، يحتوي على مؤشرات القيم غير الصفرية.
  • dense_shape : موتر 1D بالشكل [rank] ، يحدد شكل موتر.

القيمة غير الصفرية في سياق tf.SparseTensor هي قيمة غير مشفرة بشكل صريح. من الممكن تضمين القيم الصفرية بشكل صريح في values مصفوفة COO المتفرقة ، ولكن هذه "الأصفار الصريحة" لا يتم تضمينها بشكل عام عند الإشارة إلى قيم غير صفرية في موتر متفرق.

إنشاء tf.SparseTensor

dense_shape متفرقة عن طريق تحديد values indices وشكلها الكثيف مباشرةً.

import tensorflow as tf
st1 = tf.SparseTensor(indices=[[0, 3], [2, 4]],
                      values=[10, 20],
                      dense_shape=[3, 10])

عندما تستخدم وظيفة print() لطباعة موتر متفرق ، فإنها تعرض محتويات الموترات الثلاثة المكونة:

print(st1)
SparseTensor(indices=tf.Tensor(
[[0 3]
 [2 4]], shape=(2, 2), dtype=int64), values=tf.Tensor([10 20], shape=(2,), dtype=int32), dense_shape=tf.Tensor([ 3 10], shape=(2,), dtype=int64))

من الأسهل فهم محتويات الموتر المتفرق إذا كانت values غير الصفرية محاذاة مع indices المقابلة. حدد دالة مساعدة لطباعة الموترات المتناثرة بشكل جيد بحيث تظهر كل قيمة غير صفرية في السطر الخاص بها.

def pprint_sparse_tensor(st):
  s = "<SparseTensor shape=%s \n values={" % (st.dense_shape.numpy().tolist(),)
  for (index, value) in zip(st.indices, st.values):
    s += f"\n  %s: %s" % (index.numpy().tolist(), value.numpy().tolist())
  return s + "}>"
print(pprint_sparse_tensor(st1))
<SparseTensor shape=[3, 10] 
 values={
  [0, 3]: 10
  [2, 4]: 20}>

يمكنك أيضًا إنشاء موترات متفرقة من موترات كثيفة باستخدام tf.sparse.from_dense ، وتحويلها مرة أخرى إلى موترات كثيفة باستخدام tf.sparse.to_dense .

st2 = tf.sparse.from_dense([[1, 0, 0, 8], [0, 0, 0, 0], [0, 0, 3, 0]])
print(pprint_sparse_tensor(st2))
<SparseTensor shape=[3, 4] 
 values={
  [0, 0]: 1
  [0, 3]: 8
  [2, 2]: 3}>
st3 = tf.sparse.to_dense(st2)
print(st3)
tf.Tensor(
[[1 0 0 8]
 [0 0 0 0]
 [0 0 3 0]], shape=(3, 4), dtype=int32)

معالجة الموترات المتفرقة

استخدم الأدوات المساعدة في حزمة tf.sparse لمعالجة موترات متفرقة. عمليات مثل tf.math.add التي يمكنك استخدامها للمعالجة الحسابية للموترات الكثيفة لا تعمل مع الموترات المتفرقة.

أضف موترات متفرقة من نفس الشكل باستخدام tf.sparse.add .

st_a = tf.SparseTensor(indices=[[0, 2], [3, 4]],
                       values=[31, 2], 
                       dense_shape=[4, 10])

st_b = tf.SparseTensor(indices=[[0, 2], [7, 0]],
                       values=[56, 38],
                       dense_shape=[4, 10])

st_sum = tf.sparse.add(st_a, st_b)

print(pprint_sparse_tensor(st_sum))
<SparseTensor shape=[4, 10] 
 values={
  [0, 2]: 87
  [3, 4]: 2
  [7, 0]: 38}>

استخدم tf.sparse.sparse_dense_matmul لمضاعفة الموترات المتفرقة بمصفوفات كثيفة.

st_c = tf.SparseTensor(indices=([0, 1], [1, 0], [1, 1]),
                       values=[13, 15, 17],
                       dense_shape=(2,2))

mb = tf.constant([[4], [6]])
product = tf.sparse.sparse_dense_matmul(st_c, mb)

print(product)
tf.Tensor(
[[ 78]
 [162]], shape=(2, 1), dtype=int32)

ضع الموترات المتناثرة معًا باستخدام tf.sparse.concat بينها باستخدام tf.sparse.slice .

sparse_pattern_A = tf.SparseTensor(indices = [[2,4], [3,3], [3,4], [4,3], [4,4], [5,4]],
                         values = [1,1,1,1,1,1],
                         dense_shape = [8,5])
sparse_pattern_B = tf.SparseTensor(indices = [[0,2], [1,1], [1,3], [2,0], [2,4], [2,5], [3,5], 
                                              [4,5], [5,0], [5,4], [5,5], [6,1], [6,3], [7,2]],
                         values = [1,1,1,1,1,1,1,1,1,1,1,1,1,1],
                         dense_shape = [8,6])
sparse_pattern_C = tf.SparseTensor(indices = [[3,0], [4,0]],
                         values = [1,1],
                         dense_shape = [8,6])

sparse_patterns_list = [sparse_pattern_A, sparse_pattern_B, sparse_pattern_C]
sparse_pattern = tf.sparse.concat(axis=1, sp_inputs=sparse_patterns_list)
print(tf.sparse.to_dense(sparse_pattern))
tf.Tensor(
[[0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0]
 [0 0 0 0 0 0 1 0 1 0 0 0 0 0 0 0 0]
 [0 0 0 0 1 1 0 0 0 1 1 0 0 0 0 0 0]
 [0 0 0 1 1 0 0 0 0 0 1 1 0 0 0 0 0]
 [0 0 0 1 1 0 0 0 0 0 1 1 0 0 0 0 0]
 [0 0 0 0 1 1 0 0 0 1 1 0 0 0 0 0 0]
 [0 0 0 0 0 0 1 0 1 0 0 0 0 0 0 0 0]
 [0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0]], shape=(8, 17), dtype=int32)
sparse_slice_A = tf.sparse.slice(sparse_pattern_A, start = [0,0], size = [8,5])
sparse_slice_B = tf.sparse.slice(sparse_pattern_B, start = [0,5], size = [8,6])
sparse_slice_C = tf.sparse.slice(sparse_pattern_C, start = [0,10], size = [8,6])
print(tf.sparse.to_dense(sparse_slice_A))
print(tf.sparse.to_dense(sparse_slice_B))
print(tf.sparse.to_dense(sparse_slice_C))
tf.Tensor(
[[0 0 0 0 0]
 [0 0 0 0 0]
 [0 0 0 0 1]
 [0 0 0 1 1]
 [0 0 0 1 1]
 [0 0 0 0 1]
 [0 0 0 0 0]
 [0 0 0 0 0]], shape=(8, 5), dtype=int32)
tf.Tensor(
[[0]
 [0]
 [1]
 [1]
 [1]
 [1]
 [0]
 [0]], shape=(8, 1), dtype=int32)
tf.Tensor([], shape=(8, 0), dtype=int32)

إذا كنت تستخدم TensorFlow 2.4 أو أعلى ، فاستخدم tf.sparse.map_values للعمليات الأولية على القيم غير الصفرية في الموترات المتفرقة.

st2_plus_5 = tf.sparse.map_values(tf.add, st2, 5)
print(tf.sparse.to_dense(st2_plus_5))
tf.Tensor(
[[ 6  0  0 13]
 [ 0  0  0  0]
 [ 0  0  8  0]], shape=(3, 4), dtype=int32)

لاحظ أنه تم تعديل القيم غير الصفرية فقط - تظل القيم الصفرية صفرية.

بالتساوي ، يمكنك اتباع نمط التصميم أدناه للإصدارات السابقة من TensorFlow:

st2_plus_5 = tf.SparseTensor(
    st2.indices,
    st2.values + 5,
    st2.dense_shape)
print(tf.sparse.to_dense(st2_plus_5))
tf.Tensor(
[[ 6  0  0 13]
 [ 0  0  0  0]
 [ 0  0  8  0]], shape=(3, 4), dtype=int32)

استخدام tf.SparseTensor مع واجهات برمجة تطبيقات TensorFlow الأخرى

تعمل الموترات المتفرقة بشفافية مع واجهات برمجة تطبيقات TensorFlow هذه:

يتم عرض الأمثلة أدناه لعدد قليل من واجهات برمجة التطبيقات المذكورة أعلاه.

tf.keras

تدعم مجموعة فرعية من واجهة برمجة تطبيقات tf.keras المتفرقة دون عمليات الصب أو عمليات التحويل باهظة الثمن. تتيح لك Keras API تمرير الموترات المتفرقة كمدخلات لنموذج Keras. تعيين sparse=True عند استدعاء tf.keras.Input أو tf.keras.layers.InputLayer . يمكنك تمرير موترات متفرقة بين طبقات Keras ، وكذلك جعل نماذج Keras تعيدها كمخرجات. إذا كنت تستخدم موترات متفرقة في tf.keras.layers.Dense طبقات كثيفة في نموذجك ، فإنها ستنتج موترات كثيفة.

يوضح لك المثال أدناه كيفية تمرير موتر متفرق كمدخل إلى نموذج Keras إذا كنت تستخدم فقط الطبقات التي تدعم المدخلات المتفرقة.

x = tf.keras.Input(shape=(4,), sparse=True)
y = tf.keras.layers.Dense(4)(x)
model = tf.keras.Model(x, y)

sparse_data = tf.SparseTensor(
    indices = [(0,0),(0,1),(0,2),
               (4,3),(5,0),(5,1)],
    values = [1,1,1,1,1,1],
    dense_shape = (6,4)
)

model(sparse_data)

model.predict(sparse_data)
array([[-1.3111044 , -1.7598825 ,  0.07225233, -0.44544357],
       [ 0.        ,  0.        ,  0.        ,  0.        ],
       [ 0.        ,  0.        ,  0.        ,  0.        ],
       [ 0.        ,  0.        ,  0.        ,  0.        ],
       [ 0.8517609 , -0.16835624,  0.7307872 , -0.14531797],
       [-0.8916302 , -0.9417639 ,  0.24563438, -0.9029659 ]],
      dtype=float32)

tf.data

تمكّنك واجهة برمجة تطبيقات tf.data من إنشاء خطوط إدخال معقدة من قطع بسيطة قابلة لإعادة الاستخدام. هيكل البيانات الأساسي الخاص به هو tf.data.Dataset ، والذي يمثل سلسلة من العناصر التي يتكون فيها كل عنصر من مكون واحد أو أكثر.

بناء مجموعات البيانات باستخدام الموترات المتفرقة

قم ببناء مجموعات بيانات من الموترات المتفرقة باستخدام نفس الطرق المستخدمة لبناءها من tf.Tensor s أو NumPy ، مثل tf.data.Dataset.from_tensor_slices . هذا المرجع يحافظ على تناثر (أو طبيعة متفرقة) من البيانات.

dataset = tf.data.Dataset.from_tensor_slices(sparse_data)
for element in dataset: 
  print(pprint_sparse_tensor(element))
<SparseTensor shape=[4] 
 values={
  [0]: 1
  [1]: 1
  [2]: 1}>
<SparseTensor shape=[4] 
 values={}>
<SparseTensor shape=[4] 
 values={}>
<SparseTensor shape=[4] 
 values={}>
<SparseTensor shape=[4] 
 values={
  [3]: 1}>
<SparseTensor shape=[4] 
 values={
  [0]: 1
  [1]: 1}>

تجميع مجموعات البيانات وفكها باستخدام موترات متفرقة

يمكنك تجميع (دمج العناصر المتتالية في عنصر واحد) ومجموعات البيانات غير المجمعة باستخدام موترات متفرقة باستخدام أساليب Dataset.batch و Dataset.unbatch على التوالي.

batched_dataset = dataset.batch(2)
for element in batched_dataset:
  print (pprint_sparse_tensor(element))
<SparseTensor shape=[2, 4] 
 values={
  [0, 0]: 1
  [0, 1]: 1
  [0, 2]: 1}>
<SparseTensor shape=[2, 4] 
 values={}>
<SparseTensor shape=[2, 4] 
 values={
  [0, 3]: 1
  [1, 0]: 1
  [1, 1]: 1}>
unbatched_dataset = batched_dataset.unbatch()
for element in unbatched_dataset:
  print (pprint_sparse_tensor(element))
<SparseTensor shape=[4] 
 values={
  [0]: 1
  [1]: 1
  [2]: 1}>
<SparseTensor shape=[4] 
 values={}>
<SparseTensor shape=[4] 
 values={}>
<SparseTensor shape=[4] 
 values={}>
<SparseTensor shape=[4] 
 values={
  [3]: 1}>
<SparseTensor shape=[4] 
 values={
  [0]: 1
  [1]: 1}>

يمكنك أيضًا استخدام tf.data.experimental.dense_to_sparse_batch عناصر مجموعة البيانات ذات الأشكال المختلفة في موترات متفرقة.

تحويل مجموعات البيانات باستخدام موترات متفرقة

قم بتحويل وإنشاء موترات متفرقة في مجموعات البيانات باستخدام Dataset.map .

transform_dataset = dataset.map(lambda x: x*2)
for i in transform_dataset:
  print(pprint_sparse_tensor(i))
<SparseTensor shape=[4] 
 values={
  [0]: 2
  [1]: 2
  [2]: 2}>
<SparseTensor shape=[4] 
 values={}>
<SparseTensor shape=[4] 
 values={}>
<SparseTensor shape=[4] 
 values={}>
<SparseTensor shape=[4] 
 values={
  [3]: 2}>
<SparseTensor shape=[4] 
 values={
  [0]: 2
  [1]: 2}>

tf.train مثال

tf.train.Example هو ترميز protobuf قياسي لبيانات TensorFlow. عند استخدام الموترات المتفرقة مع tf.train.Example ، على سبيل المثال ، يمكنك:

  • قراءة البيانات متغيرة الطول في tf.SparseTensor باستخدام tf.io.VarLenFeature . ومع ذلك ، يجب أن تفكر في استخدام tf.io.RaggedFeature بدلاً من ذلك.

  • قراءة البيانات المتفرقة العشوائية في tf.SparseTensor باستخدام tf.io.SparseFeature ، والذي يستخدم ثلاثة مفاتيح ميزة منفصلة لتخزين indices values وشكل dense_shape .

tf.function

يحسب مصمم وظيفة tf الرسوم البيانية tf.function لوظائف Python ، والتي يمكن أن تحسن بشكل كبير من أداء كود TensorFlow الخاص بك. تعمل tf.function المتفرقة بشفافية مع كل من وظيفة tf ووظائف الخرسانة .

@tf.function
def f(x,y):
  return tf.sparse.sparse_dense_matmul(x,y)

a = tf.SparseTensor(indices=[[0, 3], [2, 4]],
                    values=[15, 25],
                    dense_shape=[3, 10])

b = tf.sparse.to_dense(tf.sparse.transpose(a))

c = f(a,b)

print(c)
tf.Tensor(
[[225   0   0]
 [  0   0   0]
 [  0   0 625]], shape=(3, 3), dtype=int32)

تمييز القيم المفقودة من القيم الصفرية

معظم العمليات على tf.SparseTensor تعالج القيم المفقودة والقيم الصفرية الصريحة بشكل مماثل. هذا حسب التصميم - من المفترض أن يتصرف جهاز tf.SparseTensor تمامًا مثل موتر كثيف.

ومع ذلك ، هناك بعض الحالات التي قد يكون من المفيد فيها التمييز بين القيم الصفرية والقيم المفقودة. على وجه الخصوص ، يتيح ذلك طريقة واحدة لتشفير البيانات المفقودة / غير المعروفة في بيانات التدريب الخاصة بك. على سبيل المثال ، ضع في اعتبارك حالة استخدام حيث يكون لديك موتر من الدرجات (يمكن أن يكون لها أي قيمة فاصلة عائمة من -Inf إلى + Inf) ، مع بعض الدرجات المفقودة. يمكنك ترميز هذا الموتر باستخدام موتر متناثر حيث تُعرف الأصفار الصريحة بدرجات صفرية ولكن القيم الصفرية الضمنية تمثل في الواقع بيانات مفقودة وليست صفراً.

لاحظ أن بعض العمليات مثل tf.sparse.reduce_max لا تعامل القيم المفقودة كما لو كانت صفرًا. على سبيل المثال ، عند تشغيل مقطع التعليمات البرمجية أدناه ، يكون الناتج المتوقع هو 0 . ومع ذلك ، وبسبب هذا الاستثناء ، يكون الناتج -3 .

print(tf.sparse.reduce_max(tf.sparse.from_dense([-5, 0, -3])))
tf.Tensor(-3, shape=(), dtype=int32)

في المقابل ، عند تطبيق tf.math.reduce_max على موتر كثيف ، يكون الناتج 0 كما هو متوقع.

print(tf.math.reduce_max([-5, 0, -3]))
tf.Tensor(0, shape=(), dtype=int32)

مزيد من القراءة والموارد