10 דברים שחובה להכיר כשעובדים עם טנסורים של pytorch

מחבר:
בתאריך:

אחת המיומנויות החשובות ביותר כשעובדים עם pytorch היא עבודה עם טנסורים. במדריך זה 10 דברים שחשוב להכיר כשעובדים עם טנסורים של pytorch.

מדריך טנסורים של pytorch

 

0. כיצד לייבא את ולהגדיר את pytorch?

import torch

נייבא בנוסף את הספרייה numpy המשמשת לעבודה עם מערכים, מטריקסים וטנסורים:

import numpy as np

חשוב להגדיר את סוג המעבד כי pytorch יכול להשתמש ב-CPU או ב-GPU בשביל לאחסן ולעבוד עם טנסורים.

device = 'cuda' if torch.cuda.is_available() else 'cpu'
  • אם קיים GPU אז pytorch ישתמש בו. אחרת ישתמש ב- CPU.
  • תזכרו את המשתנה device, סוג המעבד, כי מיד נעשה בו שימוש.

 

מי שעובד על סביבת colab ורוצה לעבוד עם ה-GPU צריך להגדיר את הסביבה כפי שהסברתי כאן.

בנוסף, בסביבת colab צריך להגדיר באופן הבא את ה device:

# set GPU device
print(torch.cuda.is_available())
device = torch.device("cuda:0")
print(device)

התוצאה:

True
cuda:0
  • מה שאומר שאתה עובד עם ה-GPU בסביבת colab.

 

1. כיצד לאתחל טנסור של pytorch?

הפונקציה torch.tensor() יוצרת טנסור, לדוגמה:

torch.tensor([1, 2, 3])

התוצאה:

tensor([1, 2, 3])

אפשר גם מערך רב מימדי (=טנסור):

torch.tensor([[1, 2, 3], [4, 5, 6]])

התוצאה:

tensor([[1, 2, 3],
        [4, 5, 6]])

הטרמינולוגיה עשויה לבלבל כיוון שלבאים משטחים אחרים השם tensor טנסור משמש למקרה של מערך שיש לו שלושה ממדים או יותר.

scalar סקלר הוא מספר יחיד שיש לו 0 ממדים.

ל-vector וקטור יש ממד 1.

ל-matrix מטריקס יש 2 ממדים.

ל-tensor טנסור יש 3 ממדים או יותר.

אבל ביקום של למידת מכונה וקטורים ומטריקסים נקראים גם הם טנסורים. כך שערכים מתחלקים פשוט ל-2: סקלרים או טנסורים.

 

ניתן להפוך כל מערך לטנסור של pytorch:

dd = [[1, 2, 3], [4, 5, 6], [7, 8, 9]]

ddt = torch.tensor(dd)

התוצאה:

tensor([[1, 2, 3],
        [4, 5, 6],
        [7, 8, 9]])

וגם להפוך מערך של numpy לטנסור של pytorch:

np_array = np.zeros((2, 2))
b = torch.from_numpy(np_array)

ברירת המחדל היא לשמור את הטנסורים על שמקורם ב-numpy על ה-CPU. כדי לשמור גם על ה- GPU, אם קיים, נשתמש בפונקציה to():

b = torch.from_numpy(np_array).to(device)

הרבה פעמים כדאי לשנות את רמת הדיוק ל-32 bit באמצעות הפונקציה float():

b = torch.from_numpy(np_array).float().to(device)

נבדוק את סוג type הטנסור:

print(type(np_array))
print(type(b))
print(b.type())
class 'numpy.ndarray'
class 'torch.Tensor'
torch.cuda.FloatTensor
  • עבור השניים הראשונים קיבלנו את מה שציפינו: numpy.ndarray לראשון ו- torch.Tensor לשני.
  • היתרון של type() שהוא מראה על איזה מעבד הטנסור קיים CPU/GPU.

כדי לעשות את הפעולה ההפוכה ולהמיר טנסור של pytorch למערך של numpy:

n = b.numpy()

זה עובד רק על טנסורים המאוחסנים על ה- CPU. במידה והטנסור נמצא על ה-GPU צריך קודם להעביר ל- CPU באמצעות המתודה cpu():

b.cpu().numpy()

 

ניתן להגדיר את סוג הנתונים מהם מורכב המערך כש- float הוא השימושי ביותר:

t1 = torch.tensor([1, 2, 3], dtype=torch.float32)

נבדוק מה קיבלנו:

print(t1)
tensor([1., 2., 3.])

ניתן לאתחל טנסור של אפסים באמצעות הפונקציה torch.zeros()

לדוגמה, ניצור מערך שלו 2 שורות ו-3 עמודות:

a = torch.zeros(2, 3)

התוצאה:

tensor([[0., 0., 0.],
        [0., 0., 0.]])
  • הפרמטר הראשון אותו מעבירים לפונקציה הוא מספר השורות והשני הוא מספר העמודות

ליצירת טנסור של אחדות נשתמש בפונקציה torch.ones().

לדוגמה, ניצור מערך הכולל 5 אחדות:

torch.ones(5)

התוצאה:

tensor([1., 1., 1., 1., 1.])

ניתן לאכלס מערך בתוך טווח באמצעות torch.arange(start,end,step).

לדוגמה, מערך בטווח 0 עד 10 המכיל חמישה פריטים עם רווחים שווים ביניהם:

torch.arange(start = 0, end = 10, step = 2)

התוצאה:

tensor([0, 2, 4, 6, 8])

אפשרות שימושית נוספת לאתחול מערך היא באמצעות הפונקציה torch.rand() היוצרת טנסור המכיל ערכים אקראיים בין 0 ל-1.

לדוגמה:

torch.rand(3,2)

התוצאה:

tensor([[0.3205, 0.6572],
        [0.8443, 0.7753],
        [0.4954, 0.1709]])

 

2. כיצד לסקור את המערכים?

נברר את הצורה shape של הטנסור:

a = torch.tensor([[1, 2, 3], [4, 5, 6]])
print(a.shape)

התוצאה:

torch.Size([2, 3])
  • מציג את מספר השורות והעמודות

לאיזה סוג שייך הטנסור?

print(type(a))
class 'torch.Tensor'

לבירור מספר המימדים בטנסור נשתמש בפונקציה ndimension():

print(a.ndimension()) # 2
  • 2 מימדים

המתודה numel() תשמש אותנו לבירור מספר האלמנטים הכולל בטנסור:

a.numel() # 6

לקבלת הערכים הייחודיים בלבד נשתמש בפונקציה unique():

a.unique()

התוצאה:

tensor([1, 2, 3, 4, 5, 6])

 

3. כיצד להמיר את סוג הטנסור type?

ניצור מערך עם 5 פריטים:

a = torch.arange(5)
print(a)

התוצאה:

tensor([0, 1, 2, 3, 4])

ההמרה הנפוצה ביותר היא למספרים עשרוניים float באמצעות:

a.float()

התוצאה:

tensor([0., 1., 2., 3.])
  • בעקבות ההמרה הערך מיוצג בזיכרון המחשב באמצעות 32 ביט

אם רוצים דיוק גבוה יותר אפשר להמיר ל-64 ביט באמצעות הפונקציה double():

a.double()

כדי להמיר למספרים שלמים (integers) בד"כ משתמשים ב:

a.short()

התוצאה היא המרה למספר שלם התופס 16 ביטים בזכרון:

tensor([0, 1, 2, 3], dtype=torch.int16)

אפשר להמיר למספרים שלמים השייכים לסוג long המאכלסים 64 ביטים:

a.long()
tensor([0, 1, 2, 3])

כדי להמיר ערכים לבוליאנים bool:

a.bool()

התוצאה:

tensor([False,  True,  True,  True,  True])
  • כל הערכים הם True מלבד 0 שהוא False

 

4. כיצד לגשת ולערוך פריטים בתוך הטנסור?

ניצור טנסור a:

a = torch.tensor([[1, 2, 3], [4, 5, 6], [7, 8, 9]])

המורכב מ- 3 שורות ו-3 עמודות:

tensor([[1, 2, 3],
        [4, 5, 6],
        [7, 8, 9]])

 

ניתן לשלוף פריט מתוך הטנסור על פי האינדקס שלו.

כדי לשלוף את הפריט הראשון מהשורה הראשונה:

print(a[0][0])
  • כיוון שמתקבל ערך יחיד אפשר לחלץ אותו באמצעות המתודה item():

התוצאה:

tensor(1)
a[0][0].item() 

התוצאה:

1

כדי לשלוף את הפריט השני מהשורה השנייה:

a[1][1] # tensor(5)

כדי לשלוף את הפריט השלישי מהשורה השנייה:

a[1][2] # tensor(6)

ואת הפריט השני מהשורה השלישית:

a[2][1] # tensor(8)

כדי לשלוף את הפריט האחרון נשתמש באינדקס 1-:

print(a[-1][-1])

התוצאה:

tensor(9)

ניתן לשלוף טווח של פריטים, לדוגמה, את שתי השורות הראשונות:

print(a[0:2])
tensor([[1, 2, 3],
        [4, 5, 6]])
  • נקודתיים מפרידים בין העמדה הפותחת והסוגרת.
  • נא לשים לב! השליפה כוללת את הפריט הראשון אבל לא את האחרון.

נשתמש בנקודותיים בלבד כדי לשלוף את הכל:

a[:]

התוצאה:

tensor([[1, 2, 3],
        [4, 5, 6],
        [7, 8, 9]])

כדי לבחור את העמודה הראשונה בלבד, נבחר את כל השורות ואת הפריט הראשון מכל שורה:

a[:, 0]

התוצאה:

tensor([[1],
        [4],
        [7]])

כדי לשלוף את פריטי העמודה השנייה:

a[:, 1]

התוצאה:

tensor([2, 5, 8])

כדי לשלוף את פריטי העמודה האחרונה:

a[:,-1]

התוצאה:

tensor([3, 6, 9])

כדי לבחור את 2 הפריטים הראשונים משתי השורות הראשונות:

a[0:2, 0:2]

התוצאה:

tensor([[1, 2],
        [4, 5]])

התחביר המשתמש לשליפה מטנסור דו מימדי הוא:

a[row_start : row_end, column_start : column_end]
  • קודם השורות ואח"כ העמודות.

כדי לשנות את ערכו של פריט צריך להציב את הערך הרצוי.

לדוגמה, נשנה את ערכו של הפריט האחרון בטנסור:

a[-1, -1] = 101

print(a)
tensor([[  1,   2,   3],
        [  4,   5,   6],
        [  7,   8, 101]])

 

5. טנסורים ותנאים

כשעובדים עם טנסורים של pytorch קל מאוד לערוך השוואות.

לדוגמה, אילו פריטים בטנסור הבא גדולים מ-2.5?

a = torch.tensor([[1, 2, 3], [4, 5, 6]])

a > 2.5

התוצאה:

tensor([[False, False,  True],
        [ True,  True,  True]])

קל מאוד לשלוף את פריטי הטנסור העומדים בתנאי:

a[a > 2.5]

התוצאה:

tensor([3, 4, 5, 6])

אפשר לשכלל את התנאי באמצעות אופרטורים:

  • | - אופרטור "או"
  • & - אופרטור "גם"

לדוגמה:

[(a > 2.5) & (a < 5)]
[(a <= 2) | (a > 5)]

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

לדוגמה, להעלות בריבוע את כל הערכים הגדולים מ-3:

torch.where(a <= 3, a,  a**2)

התוצאה:

tensor([[ 1,  2,  3],
        [16, 25, 36]])

 

6. כיצד לעשות פעולות חשבון על טנסורים?

אפשר לעשות פעולות חשבון על טנסורים של pytorch.

a = torch.tensor([1, 2, 3])
b = torch.tensor([4, 5, 6])

כדי לחבר טנסורים בעלי אותה צורה אפשר להשתמש במתודה torch.add():

torch.add(a, b)

ולקבל:

tensor([5, 7, 9])

התוצאה היא חיבור של כל אחד מהאלמנטים בטנסור הראשון במקבילו בטנסור השני element-wise addition.

באופן דומה, ניתן להוסיף סקלר (מספר יחיד):

torch.add(a, 2) # tensor([3, 4, 5])

אפשר להסתפק בסימן החיבור:

a + b

ולקבל את אותה התוצאה:

tensor([5, 7, 9])

וגם:

a + 2 # tensor([3, 4, 5])

נשתמש במתודה torch.sub() כדי לחסר בין טנסורים:

torch.sub(a, b)

התוצאה:

tensor([-3, -3, -3])

ואפשר גם להסתפק בסימן החיסור:

a - b # tensor([-3, -3, -3])

וכרגיל בפעולות החשבון אפשר לחסר סקלר:

a - 1 # tensor([0, 1, 2])

במתודה torch.mul() נשתמש כדי לכפול טנסורים.

torch.mul(a, b)

התוצאה:

tensor([ 4, 10, 18])

ואפשר, אם רוצים, להשתמש בקיצור:

a * b # tensor([ 4, 10, 18])

המתודה torch.div() משמשת לחלוקה:

torch.div(a, b) # tensor([0.2500, 0.4000, 0.5000])

אפשר להסתפק בסימן החלוקה כדי להשיג את אותה התוצאה:

a / b # tensor([0.2500, 0.4000, 0.5000])

כדי לחלק ולקבל מספרים שלמים צריך להעביר את הפרמטר rounding_mode לפונקציה div():

torch.div(a, 2, rounding_mode='floor')

כדי להעלות בחזקה אפשר להשתמש במתודה pow().

לדוגמה, נעלה טנסור בחזקה שנייה:

a.pow(2) # tensor([1, 4, 9])

גם לזה יש קיצור:

a ** 2 # tensor([1, 4, 9])

כדי לקבל את הערך האבסולוטי נשתמש בפונקציה abs():

c = torch.tensor([-1, 2, -3])
torch.abs(c) # tensor([1, 2, 3])

 

7. שינוי צורה של טנסורים - reshape, טרנספוזיציה ו-squeeze

הרבה פעמים אנחנו צריכים לשנות את הצורה של טנסור. 

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

נתחיל מוקטור שצורתו 2 שורות ו-3 עמודות:

a = torch.tensor([[1, 2, 3], [4, 5, 6]])
tensor([[1, 2, 3],
        [4, 5, 6]])

נפעיל את המתודה transpose() המקבלת שני פרמטרים:

  • הציר הראשון של הטרנספוזיציה
  • הציר השני של הטרנספוזיציה
a.transpose(0, 1)
tensor([[1, 4],
        [2, 5],
        [3, 6]])

במתודה torch.reshape() נשתמש כדי לשנות את הצורה של מערך.

הצורה של מערך shape היא מספר הפרטים בכל אחד מהממדים שלו. כשאנו משנים את הצורה של המערך אנחנו מוסיפים או מפחיתים ממדים, ומשנים את מספר הפריטים בממדים.

בדוגמה הבאה נמיר מערך של ממד אחד ובו 6 פריטים לממד אחד ובו שישה פרטים:

a = torch.tensor([[1, 2, 3], [4, 5, 6]])
a.reshape(1, 6)

התוצאה:

tensor([[1, 2, 3, 4, 5, 6]])
  • הפרמטר הראשון הוא מספר השורות
  • השני הוא מספר העמודות

הצורה המתקבלת היא:

print(a.reshape(1, 6).shape) # torch.Size([1, 6])

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

נתחיל מטנסור בן 27 פריטים במימד אחד:

a = torch.arange(1, 28)

ונעביר את 1- בתור הפרמטר השלישי לפונקציה reshape כי אנחנו מעוניינים שהפונקציה תחשב את צורתו של המימד השלישי בעצמה.

התוצאה:

a.reshape(3, 3, -1)
tensor([[[ 1,  2,  3],
         [ 4,  5,  6],
         [ 7,  8,  9]],

        [[10, 11, 12],
         [13, 14, 15],
         [16, 17, 18]],

        [[19, 20, 21],
         [22, 23, 24],
         [25, 26, 27]]])

דרך נוספת לשינוי צורה של טנסורים היא באמצעות squeeze ו-unsqueeze.

  • squeeze - מסיר את הממד (או הציר) של הטנסור שיש לו אורך 1.
  • unsqueeze - מוסיף מימד שיש לו אורך 1.

פונקציות אילו מאפשרות לנו להרחיב או לצמצם את מספר הממדים. נראה אותם בפעולה.

נתחיל מטנסור פשוט (שורה אחת ו-3 פריטים):

a = torch.Tensor([1,2,3]) 
print(a) # tensor([1., 2., 3.])
print(a.shape) # torch.Size([3]) # torch.Size([3])

נשתמש במתודה unsqueeze() כדי להוסיף מימד אחד בציר 0:

# Unsqueeze the tensor in dimension 0
a0 = torch.unsqueeze(a, dim = 0) 

התוצאה:

print(a0)
print(a0.shape)
tensor([[1., 2., 3.]])
torch.Size([1, 3])
  • הוספנו ממד אחד בציר 0.

כדי להסיר את הממד היחיד בציר ה-0 נשתמש במתודה squeeze():

# Squeeze the tensor in dimension 0
a0_back = torch.squeeze(a0, dim = 0)

התוצאה:

print(a0_back)
print(a0_back.shape)
tensor([1., 2., 3.])
torch.Size([3])
  • הסרנו את הממד היחיד בציר ה-0.

שיטוח של טנסור הוא הסרה של כל הממדים והשארת אחד. 

כדי לשטח את הטנסור נשתמש בפונקציה:

def flatten(x):
    x = x.reshape(1, -1)
    x = x.squeeze()
    return x

נבדוק את הפונקציה על טנסור:

a = torch.arange(1, 10).reshape(3, 3)

שצורתו:

print(a)
tensor([[1, 2, 3],
        [4, 5, 6],
        [7, 8, 9]])

נעביר לפונקציה:

f = flatten(a)

התוצאה:

print(f)
tensor([1, 2, 3, 4, 5, 6, 7, 8, 9])

והצורה shape היא:

f.shape # torch.Size([9])

הפונקציה משלבת את המתודות reshape ו-squeeze.

הפרמטר 1- שהעברנו לפונקציה reshape אומר ל-pytorch לחשב את אורך המימד החסר בהתבסס על צורת הטנסור בעוד המימד הראשון הוא 1

פעולת ה-squeeze מסירה את הממד או הציר בעל אורך 1.

התוצאה היא טנסור חד מימדי באורך 9.

 

8. כפל מטריצות ומוצר נקודה ב-pytorch

התנאי לכפל טנסורים (=מטריצות) הוא שמספר העמודות של הראשון יהיה שווה למספר השורות של השני.

אם צורת הראשון הוא (n X m ) אז צורת השני צריך להיות (m X p).

הצורה מאוד חשובה!

נשתמש במתודה torch.mm() לצורך כפל מטריצות.

לדוגמה נכפול בכפל מטריצות את הטנסור v:

v = torch.tensor([[1, 2, 3]])

שצורתו:

v.shape # torch.Size([1, 3])
  • שורה 1 ו-3 עמודות

בטנסור m:

m = torch.tensor([[2, 3], [5, 6], [4, 1]])

שצורתו:

m.shape # torch.Size([3, 2])
  • 3 שורות ו-2 עמודות
torch.mm(v, m)

התוצאה:

tensor([[24, 18]])

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

תוצר הנקודה dot product כופל את האלמנטים המקבילים של שני וקטורים (טנסורים חד ממדיים) שיש להם את אותו האורך ומחזיר תוצאה אחת.

נכפיל את v1 שהוא מערך חד ממדי של 3 ערכים:

v1 = torch.tensor([1, 3, 4], dtype=torch.float)
tensor([1., 3., 4.])

בטנסור חד-ממדי v2 בעל אורך זהה:

v2 = torch.ones(1, 3).reshape(-1)
tensor([1., 1., 1.])

תוצר הנקודה הוא:

torch.dot(v1, v2) # tensor(8.)

 

9. פעולות גלובליות על טנסורים

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

a = torch.arange(1, 10).reshape(3, 3)
tensor([[1, 2, 3],
        [4, 5, 6],
        [7, 8, 9]])

כדי לחשב את הממוצע נשתמש במתודה mean():

torch.mean(a.float())

התוצאה:

tensor(5.)
  • חשוב להקפיד להעביר ערכים מסוג מספר עשרוני float

ניתן לציין את הממד עליו יש לבצע את הפעולה:

torch.mean(a.float(), dim=0)

התוצאה:

tensor([4., 5., 6.])

ניתן לסכום באמצעות הפונקציה sum():

torch.sum(a.float(), dim=0)
tensor([12., 15., 18.])

ולחשב את סטיית התקן:

torch.std(a.float())
tensor(2.7386)

או את השונות:

torch.var(a.float())
tensor(7.5000)

הפונקציות min() ו-max() משמשות למציאת מינימום ומקסימום ומחזירות שני ערכים:

values, indices = torch.max(a, dim=1)
print(values, indices)

התוצאה:

tensor([3, 6, 9]) tensor([2, 2, 2])

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

כדי למצוא את האינדקסים בלבד, נשתמש ב-argmax() וב-argmin():

torch.argmax(a, dim=1)
tensor([2, 2, 2])

 

10. שימוש בגרדיאנטים

מה ההבדל בין טנסורים המכילים מידע לבין כאלו המשמשים כמשקלות ופרמטרים בתהליך למידת מכונה? הטנסורים מהסוג השני דורשים חישוב של הגרדיאנטים, שבמהלכו הערך שלהם מתעדכן. בשביל זה צריך להגדיר את הפרמטר requires_grad=True כחלק מהגדרת הטנסור.

נגדיר עבור הטנסור את הפרמטר requires_grad:

a = torch.zeros(3, requires_grad=True, dtype=torch.float32, device=device)
  • הפרמטר requires_grad=True מורה ל-pytorch לחשב את הגרדיאנט עבור הטנסור. חישוב הגרדיאנט הוא חיוני כשאנחנו רוצים לעשות אופטימיזציה לטנסור, כחלק מהותי של תהליך למידת מכונה.
  • הפרמטר dtype=torch.float32 מגדיר את סוג הנתונים
  • הפרמטר device מאחסן את הטנסור על ה- CPU או ה- GPU, אם קיים. חשוב להעביר את הפרמטר הזה כבר כשיוצרים את הטנסורים כדי לחסוך כאבי ראש בהמשך.

לכל המדריכים בנושא של למידת מכונה

 

אהבתם? לא אהבתם? דרגו!

0 הצבעות, ממוצע 0 מתוך 5 כוכבים

 

 

הוסף תגובה חדשה

 

= 3 + 4