تنسور تصادفی در پایتورچ
بهنام خدا، سلام… با چهارمین جلسه از مجموعه آموزش پایتورچ در خدمت شما هستیم. در این جلسه میخواهیم به شما نحوه ساخت تنسور تصادفی در پایتورچ را آموزش دهیم. در این جلسه با دستورهای rand ، randn ، randperm و randint در پایتورچ آشنا خواهید شد. فهرست مطالب این جلسه را در ادامه میتوانید مشاهده کنید…
مقدمهای بر تنسور تصادفی در پایتورچ
در جلسه دوم، با نحوه ساخت تنسور در پایتورچ آشنا شدید. در جلسه سوم، نحوه تعریف تنسورهای خاص در پایتورچ را آموختید. همچنین، گفتیم که تنسورهای تصادفی هم زیرمجموعه تنسورهای خاص هستند. اما به دلیل اهمیت زیادشان یک جلسه جداگانه برای تنسورهای تصادفی در نظر گرفتهایم.
دستور rand در پایتورچ
دستور rand در پایتورچ ، یک تنسور با اعداد تصادفی با توزیع یکنواخت در بازه (0,1] به سایز دلخواه میسازد. خوشبختانه، ورودیهای این دستور بسیار به ورودیهای دستورهای جلسه قبل مانند ones و zeros شبیه هست. بنابراین، دو ورودی size و dtype در اینجا هم وجود دارد. توضیح بیشتر نیاز نیست، مثالهای زیر چند نمونه تنسور تصادفی یکنواخت را نشان میدهد:
>>> rand1d = torch.rand(5) >>> print(rand1d) tensor([0.5913, 0.9750, 0.4829, 0.9838, 0.3665]) >>> rand2d = torch.rand(5, 3) >>> print(rand2d) tensor([[0.0879, 0.8208, 0.5033], [0.6882, 0.6809, 0.7722], [0.9343, 0.7642, 0.0316], [0.4021, 0.1791, 0.6036], [0.9380, 0.4427, 0.5006]])
نمیتوانیم چنین تنسوری را بسازیم. به کد زیر و خطای بهجودآمده توجه کنید. با خواندن متن خطا، بهسادگی میتوان فهمید که تعریف int8 در دستورهای random امکانپذیر نیست. طبیعی است، وقتی همه اعداد بین 0 تا 1 است، دیگر داشتن عدد صحیح معنی ندارد!>>> rand3d = torch.rand(2, 4, 3, dtype=torch.int8)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
RuntimeError: _th_uniform_ not supported on CPUType for Char
بیایید یک تنسور به طول 1000 بسازیم و سپس با استفاده از plt.hist در matplotlib هیستوگرام تنسورمان را ترسیم کنیم. میخواهیم تعداد رخدادهای اعداد با سایز bin 25 را ببینیم. وقتی میگوییم تنسور ساختهشده توزیع یکنواخت در بازه (0,1] دارد، یعنی عددی مانند 0.5 همان قدر شانس دارد که عدد 0.1 دارد. در تصویر زیر مشاهده میکنید که هیستوگرام تقریبا مسطح هست. یعنی همه اعداد تقریبا بهصورت مساوی انتخاب شدهاند. مثلا از 10000 عدد تصادفی انتخاب شده، دو عدد 0.4 و 0.8 تقریبا 400 بار انتخاب شدهاند.
>>> import matplotlib.pyplot as plt >>> rand1d = torch.rand(10000) >>> plt.hist(rand1d, 25, color='red') >>> plt.xlabel('X~U[0,1]') >>> plt.ylabel('Count') >>> plt.title("Uniform Distribution Histogram (Bin size 25)") >>> plt.grid(True)
دستور randn در پایتورچ
هنگامی که صحبت از توزیع یکنواخت میشود، صحبت از توزیع گوسی یا نرمال قابل انتظار است. دستور randn در پایتورچ ، یک تنسور تصادفی با توزیع نرمال یا گوسی میسازد. ورودیهای دستور randn شبیه دستور rand است. در کدهای زیر چند نمونه مثال از دستور randn را مشاهده میکنید:
>>> randn2d = torch.randn(2,3) >>> print(randn2d) tensor([[-2.3771, 0.8156, 1.6274], [ 0.6269, -1.0784, -0.8099]]) >>> randn3d = torch.randn(2,1,3,1) >>> print(randn3d) tensor([[[[-1.3776], [ 0.0562], [-1.6828]]], [[[ 0.6776], [ 0.7455], [ 0.2880]]]])
نکته حتما میدانید که دو پارامتر میانگین و انحراف معیار در توزیع گوسین نقش اساسی ایفا میکنند. در دستور randn پایتورچ مقدار میانگین 0 و انحراف معیار بهترتیب برابر با 0 و 1 است.
حال بیایید یک تنسور تصادفی نرمال به طول 10000 تعریف کنیم و با matplotlib هیستوگرامی مشابه rand رسم کنیم. کد و شکل زیر کاملا گویای توزیع نرمال با میانگین و انحراف معیار 1 است.
>>> import matplotlib.pyplot as plt >>> randn1d = torch.randn(100000) >>> plt.hist(randn1d, 100, color='red') >>> plt.xlabel('X~N(0,1)') >>> plt.ylabel('Count') >>> plt.title("Normal Distribution Histogram (Bin size 100)") >>> plt.grid(True)
>>> import matplotlib.pyplot as plt
>>> rand1d = torch.rand(10000) * 2.5 + 2.0
>>> plt.hist(rand1d, 25, color='blue')
>>> plt.xlabel('X~U[0,1]')
>>> plt.ylabel('Count')
>>> plt.title("Uniform Distribution Histogram (Bin size 25)")
>>> plt.grid(True)
دستور randint در پایتورچ
دستور randint در پایتورچ تنسور رندوم با مقادیر صحیح ( Integer ) میسازد. اعداد تصادفی ساختهشده توزیع یکنواخت دارند. در بالا هم دیدیم که rand تنسور رندم با توزیع یکنواخت میساخت. دستور randint سه ورودی مهم دارد. یکی از این ورودیها، سایز تنسور است. دو ورودی دیگر، ابتدا و انتهای بازه اعداد است. یعنی دستور randint از شما یک بازه میخواهد تا اعدادی تولید کند که در این بازه قرار داشته باشند. در کد زیر، یک تنسور 3×4 ساختهایم که تمامی اعداد آن در بازه 0 تا 10 است:
>>> randint2d = torch.randint(0, 10, (4,3)) >>> print(randint2d) tensor([[4, 4, 6], [6, 5, 4], [7, 1, 2], [3, 9, 3]])
در دستور بالا، ورودی اول نشاندهنده ابتدای بازه، ورودی دوم معادل انتهای بازه است. احتمالا میدانید که ورودی سوم هم سایز تنسور را نشان میدهد. ورودی اول یا همان ابتدای بازه، یک مقدار پیشفرض (مقدار 0) دارد. بنابراین، اگر بجای دستور بالا، دستور پایین را بنویسیم و فقط مقدار انتهای بازه و سایز تنسور را مشخص کنیم، بازهم کافی است:
>>> randint2d = torch.randint(10, (4,3)) >>> print(randint2d) tensor([[8, 6, 0], [0, 2, 8], [7, 8, 1], [9, 6, 2]])
در مثال زیر، مقدار متفاوتی از صفر برای ورودی اول درنظر گرفتهایم:
>>> randint2d = torch.randint(3, 10, (4,3)) >>> print(randint2d) tensor([[5, 8, 9], [3, 3, 5], [8, 8, 4], [4, 8, 4]])
توجه در دستور randint اگر مقدار انتهایی بازه برابر با n باشد، بزرگترین عدد تصادفی تولید شده در تنسور n-1 خواهد بود. یعنی بازه در دستور randint بهصورت (p,n] خواهد بود. یعنی، اعداد تصادفی تولید شدهی بین بازه p و n، شامل خود p است، اما شامل n نیست. این را هم بگوییم که در بسیاری از دستورهای پایتون و پایتورچ این حالت (p,n] برقرار است.
>>> randint2d = torch.randint(-10, 10, (1,2,4))
>>> print(randint3d)
tensor([[[ -6, 3, 3, -10],
[ 7, -9, 4, 2]]])
>>> print(randint3d.dtype)
torch.int64
دستور randperm در پایتورچ
چهارمین دستور ساخت تنسور تصادفی در پایتورچ ، randperm است. دستور randperm یک تنسور از اعداد تصادفی صحیح میسازد. خب شبیه قبلی شد؟ تا حدی بله، اما تفاوتهای زیادی هم وجود دارد. اول اینکه، این دستور تنها تنسور یک بعدی میسازد. دوم اینکه، این دستور تنها یک ورودی لازم دارد. براساس عدد ورودی، دستور randperm یک جایگشت تصادفی از اعداد میسازد. یعنی، اگر عدد ورودی n باشد، آنگاه دستور randperm از 0 تا n-1 تمامی اعداد را به شما با ترتیبی تصادفی میدهد. یک مثال همه چیز را بهتر مشخص میکند. ما یک مجموعه عدد تصادفی از 0 تا 10 میخواهیم. طوریکه همه اعداد باشند، اما اعداد تصادفی انتخاب شوند. در کد زیر بهراحتی این کار انجام شده است:
>>> randp = torch.randperm(10) >>> print(randp) tensor([2, 3, 7, 6, 5, 0, 9, 8, 4, 1])
راحتتر بگوییم، ما یکسری گوی با شماره 0 تا 100 داریم که آنها را در کیسه ریختهایم. حال، بعد از بههم زدن میخواهیم یکییکی همه آنها را بیرون بیاوریم. در بالا به دو تفاوت اساسی بین دستور randint و randperm اشاره کردهایم. اما یک تفاوت اساسی دیگر هم وجود دارد که شاید به آن پی نبرده باشید. به نکته زیر دقت کنید…
نکته دستور randint هم میتواند یک تنسور یک بعدی در بازه 0 تا n تولید کند. اما نکته اینجاست که لزوما همه اعداد انتخاب نمیشوند. ممکن است یک عدد در بازه 0 تا n چندبار انتخاب شود و یک عدد هم اصلا انتخاب نشود. اما، در randperm اینطور نیست و همه اعداد در بازه 0 تا n حداقل و حداکثر 1 بار انتخاب میشوند.
به تنسور randi دقت کنید؛ این تنسور شامل اعداد تکراری است. مثلا عدد 3 و 6 چند بار تکرار شدهاست. ضمنا خبری از عدد 14 نیست. اما به تنسور randp نگاه کنید؛ همه اعداد از 0 تا 14 وجود دارند و یک بار هم تکرار شدهاند. این هم از تفاوت سوم randint و randperm…>>> randi = torch.randint(15, (15,))
>>> print(randi)
tensor([13, 6, 14, 1, 3, 10, 4, 13, 6, 2, 3, 0, 6, 7, 12])
>>> randp = torch.randperm(15)
>>> print(randp)
tensor([ 5, 9, 0, 6, 11, 14, 13, 12, 4, 7, 3, 2, 8, 1, 10])
دستور seed در پایتورچ
آخرین بخش از جلسه تنسور تصادفی در پایتورچ با بخشهای قبلی کمی متفاوت است. دستور seed بسیار در بحث یادگیری ماشین و عمیق مهم است. دستور زیر را چندبار اجرا کنید:
>>> randp = torch.randperm(15) >>> print(randp) tensor([ 7, 12, 8, 14, 10, 4, 1, 6, 9, 11, 0, 3, 13, 2, 5]) tensor([12, 10, 1, 9, 4, 8, 5, 6, 13, 3, 7, 0, 2, 14, 11]) tensor([ 3, 6, 2, 1, 7, 10, 8, 4, 0, 11, 12, 5, 14, 13, 9])
مشاهده میکنید که بدون اینکه شما ورودیها را تغییر دهید، خروجی آن هربار متفاوت است. خب انتظار هم همین است، قراراست اعداد تصادفی بسازیم! دستور seed در پایتورچ باعث میشود که اعداد تصادفی ساختهشده با چندین بار اجرا، همواره همان اعداد تصادفی قبلی را بدهند! برای این منظور باید از دستور manual_seed در پایتورچ استفاده کنید. در مثال زیر، یک عدد دلخواه برای seed تنظیم کردهایم و بعد یک تنسور تصادفی ساختهایم. این دستور را هرچقدر اجرا کنید، همواره یکسری اعداد تصادفی ثابت میدهد.
>>> torch.manual_seed(8) >>> randp = torch.randperm(15) >>> print(randp) tensor([ 3, 13, 9, 12, 7, 11, 1, 10, 0, 4, 6, 8, 2, 5, 14]) tensor([ 3, 13, 9, 12, 7, 11, 1, 10, 0, 4, 6, 8, 2, 5, 14]) tensor([ 3, 13, 9, 12, 7, 11, 1, 10, 0, 4, 6, 8, 2, 5, 14])
کاربرد این دستور کجاست؟ بهصورت خلاصه یک مثال برایتان میآوریم؛ تصور کنید ما یک دیتاست اعداد از 0 تا 9 داریم. مثلا دیتاست mnist یا دیتاست دستخط اعداد که در تصویر زیر هم نشان دادهایم.
فرض کنید، این دیتاست شامل 1000 تصویر است که برای هر عدد 100 تصویر وجود دارد. حال ما دو دسته داده میخواهیم: دادههای آموزش ( train ) و دادههای ارزیابی ( test ). خب با استفاده از دستوری مانند randperm میتوانیم بهراحتی این دیتاست 1000-تایی را به دو دسته 500-تایی تقسیم کنیم. به صورت زیر:
>>> randp = torch.randperm(1000) >>> print(randp) tensor([838, 294, 575, ..., 406, 438, 418])
اما مشکل اینجاست که اعداد ساختهشده همواره درحال تغییر هستند. یعنی شما هربار که الگوریتمتان را اجرا کنید، دادههای آموزش و ارزیابی همواره درحال تغییر هستند و این خوب نیست. ما میخواهیم هربار که اجرا میکنیم، دادههای آموزش و ارزیابی ثابت باشد. چرا؟ خب ما درحال بهبود الگوریتممان هستیم. باید بدانیم این تغییرات جدیدی که در الگوریتم ایجاد کردهایم، باعث شده دقت نسبت به قبل بهتر شود یا خیر؟! اگر همواره دادههای آموزش و ارزیابی عوض شود که نمیتوانیم بفهمیم… اینجاست که تنظیم seed به کمک ما میآید و با هربار اجرا یک مجموعه عدد تصادفی ثابت در اختیار ما قرار میدهد. بنابراین، دیگر دادههای آموزش و ارزیابی با هربار اجرا عوض نمیشوند.
نکته دستور seed برای همه دستورهای ساخت تنسور تصادفی در پایتورچ که در این جلسه معرفی کردیم، صادق است.
نکته اعداد seed هیچ محدودیتی ندارد. هرعددی که خواستید انتخاب کنید. اما بعد از انتخاب یک عدد، دیگر در توسعه الگوریتمتان آنرا تغییر ندهید.
>>> torch.manual_seed(25)
>>> randn3d = torch.randn(2, 2, 1)
>>> print(randn3d)
tensor([[[1.1781], [0.1056]],
[[1.5313], [0.6305]]])
جلسه چهارم هم تمام شد. همانطور که قبلا گفتیم، نامپای ( numpy ) در یادگیری ماشین و یادگیری عمیق نقش کلیدی دارد. اما خوشبختانه PyTorch یک numpy در دل خودش دارد. بههمین دلیل، جلسات ابتدایی را به آموزش دستورات مشابه نامپای در پایتورچ اختصاص دادهایم. حوصله کنید، به جلسات یادگیری عمیق هم خواهیم رسید. با ما در ارتباط باشید و سوالات و نظرات خود را کامنت کنید.
مطالب زیر را حتما مطالعه کنید
شبکه عصبی mlp
شبکه ترنسفورمر
مدل MobileLLM
یادگیری عمیق چیست
آموزش یادگیری عمیق رایگان
شبکه عصبی کانولوشن
2 دیدگاه
به گفتگوی ما بپیوندید و دیدگاه خود را با ما در میان بگذارید.
سلام با تشکر از آموزش خوبتون . خیلی استفاده کردم. می خواستم یک نکته ای رو متذکر بشم ، در جلسه ی چهرم آموزش پایتورچ، کد مربوط به نمودار بردار رندوم یک اشتباه کوچک داره.
کد ذکر شده:
import matplotlib.pyplot as plt
rand1d = torch.rand(10000)
print(rand1d.shape[0])
plt.hist(rand1d, 25, color=’red’)
plt.xlabel(‘X~U[0,1]’)
plt.ylabel(‘Count’)
plt.title(“Uniform Distribution Histogram (Bin size 25)”)
plt.grid(True)
اجرای این کد با خطا روبه رو میشه چون برداری که تولید میشه ، یک بردار عمودی است و نمی توان با یک رنگ آن را کشید.بنابر ین باید ریشیپ بشه و به یک بردار افقی تبدیل بشه که کد درست آن اورده شده است.
کد اصلاحی:
import matplotlib.pyplot as plt
rand1d = torch.rand(10000)
rand1d = torch.reshape(rand1d,(1,10000))
print(rand1d.shape[0])
plt.hist(rand1d, 25, color=’red’)
plt.xlabel(‘X~U[0,1]’)
plt.ylabel(‘Count’)
plt.title(“Uniform Distribution Histogram (Bin size 25)”)
plt.grid(True)
سلام
خوشحالیم که از آموزش راضی بودید.
بابت تذکر اشکال هم بسیار بسیار ممنون 🌹🙏