Takip et

PyTorch Kurulumu ve Kullanımı: Derin Öğrenmeye Kapsamlı Bir Giriş

PyTorch Kurulumu ve Kullanımı: Derin Öğrenmeye Kapsamlı Bir Giriş Giriş Derin öğrenme, yapay zekanın en hızlı gelişen ve en etkili alanl

PyTorch Kurulumu ve Kullanımı: Derin Öğrenmeye Kapsamlı Bir Giriş

Giriş

Derin öğrenme, yapay zekanın en hızlı gelişen ve en etkili alanlarından biridir. Görüntü tanıma, doğal dil işleme, ses sentezi ve özerk sürüş gibi birçok alanda çığır açan başarılara imza atmıştır. Bu başarının arkasında, karmaşık nöral ağ modellerini tasarlamak, eğitmek ve dağıtmak için kullanılan güçlü yazılım kütüphaneleri yatmaktadır. Bu kütüphaneler arasında PyTorch, esnekliği, Pythonic yapısı ve yüksek performansı sayesinde araştırmacılar ve geliştiriciler arasında hızla popülerlik kazanmıştır.

PyTorch, Facebook’un yapay zeka araştırma grubu tarafından geliştirilen açık kaynaklı bir makine öğrenimi kütüphanesidir. Temel olarak, NumPy benzeri bir tensor kütüphanesi üzerine kurulmuştur ve GPU hızlandırması ile derin öğrenme modellerinin eğitimini kolaylaştırır. Dinamik hesaplama grafikleri (define-by-run) yaklaşımı sayesinde, model geliştirme sürecinde daha fazla esneklik sunar. Bu, özellikle karmaşık veya deneysel model mimarileri üzerinde çalışan araştırmacılar için büyük bir avantajdır. PyTorch’un sezgisel API’si, Python diline aşina olan herkesin derin öğrenme projelerine kolayca başlamasını sağlar. Bu makalede, PyTorch’u nasıl kuracağınızı, temel bileşenlerini nasıl kullanacağınızı ve basit bir derin öğrenme modelini nasıl oluşturup eğiteceğinizi ayrıntılı bir şekilde inceleyeceğiz.

PyTorch Kurulumu

PyTorch’u sisteminize kurmak, derin öğrenme yolculuğunuzdaki ilk adımdır. Kurulum süreci, işletim sisteminize, kullanmak istediğiniz Python ortamına ve özellikle GPU desteği isteyip istemediğinize göre değişiklik gösterebilir.

Sistem Gereksinimleri

PyTorch’u verimli bir şekilde kullanabilmek için belirli sistem gereksinimlerini karşılamanız önemlidir:

  • İşletim Sistemi: Windows, macOS veya Linux.
  • Python Sürümü: Genellikle Python 3.7 ve üzeri sürümler desteklenir. En güncel kararlı sürümü kullanmanız önerilir.
  • Paket Yöneticisi: pip (Python’ın standart paket yöneticisi) veya conda (Anaconda dağıtımı ile gelen bir ortam ve paket yöneticisi). Conda, özellikle bilimsel hesaplama ve derin öğrenme ortamları için daha iyi bir izolasyon ve bağımlılık yönetimi sunar.
  • GPU Desteği (İsteğe Bağlı ama Şiddetle Önerilir): Eğer NVIDIA GPU’nuz varsa, CUDA teknolojisini kullanarak PyTorch’un GPU hızlandırmasından faydalanabilirsiniz. Bunun için:
    • Uyumlu bir NVIDIA GPU (genellikle GTX 10 serisi ve üzeri, RTX serisi).
    • NVIDIA’nın güncel GPU sürücüleri.
    • CUDA Toolkit (PyTorch’un desteklediği bir sürüm).
    • cuDNN (CUDA Deep Neural Network library), CUDA Toolkit ile birlikte gelir ve derin öğrenme işlemlerini daha da optimize eder.

    CPU üzerinde de PyTorch çalıştırılabilir ancak büyük modellerin eğitimi veya geniş veri kümeleri için çok yavaş olacaktır.

Ortam Kurulumu

Derin öğrenme projeleri genellikle belirli kütüphane versiyonlarına bağımlıdır. Bu bağımlılık çakışmalarını önlemek ve projelerinizi izole etmek için sanal ortamlar kullanmak en iyi pratiktir.

Conda ile Sanal Ortam Oluşturma

Anaconda veya Miniconda yüklüyse:

conda create -n pytorch_env python=3.9
conda activate pytorch_env

Bu komut, pytorch_env adında Python 3.9’u kullanan yeni bir sanal ortam oluşturur ve bu ortama geçiş yapar.

venv ile Sanal Ortam Oluşturma

Python’ın kendi sanal ortam modülü olan venv‘i kullanmak için:

python -m venv pytorch_env
source pytorch_env/bin/activate  # Linux/macOS
pytorch_env\Scripts\activate     # Windows

Bu komutlar da benzer şekilde bir sanal ortam oluşturur ve etkinleştirir.

PyTorch Kurulum Komutları

Ortamınızı etkinleştirdikten sonra, PyTorch’un resmi web sitesindeki kurulum talimatlarını takip etmek en güvenilir yoldur. PyTorch, size işletim sisteminize, paket yöneticinize, Python sürümünüze ve özellikle CUDA sürümünüze göre özelleştirilmiş bir komut sunar.

GPU Destekli Kurulum (Önerilen)

Eğer NVIDIA GPU’nuz ve uyumlu bir CUDA sürümünüz varsa (örneğin CUDA 11.8):

# Conda için
conda install pytorch torchvision torchaudio pytorch-cuda=11.8 -c pytorch -c nvidia

Pip için

pip install torch torchvision torchaudio --index-url https://download.pytorch.org/whl/cu118

Buradaki cu118 ifadesi CUDA 11.8 sürümünü temsil eder. Kendi sisteminizdeki CUDA sürümüne göre bu kısmı değiştirmelisiniz. torchvision ve torchaudio kütüphaneleri, sırasıyla bilgisayar görüşü ve ses işleme için PyTorch ile entegre çalışan popüler yardımcı kütüphanelerdir.

CPU Destekli Kurulum

Eğer GPU’nuz yoksa veya GPU kullanmak istemiyorsanız, sadece CPU sürümünü kurabilirsiniz:

# Conda için
conda install pytorch torchvision torchaudio cpuonly -c pytorch

Pip için

pip install torch torchvision torchaudio --index-url https://download.pytorch.org/whl/cpu

Kurulumu Doğrulama

Kurulum tamamlandıktan sonra, Python etkileşimli kabuğunu açarak PyTorch’un doğru şekilde kurulduğunu ve GPU’nuzun tanınıp tanınmadığını kontrol edebilirsiniz:

import torch
print(torch.__version__)
print(torch.cuda.is_available())
print(torch.cuda.device_count())
if torch.cuda.is_available():
    print(torch.cuda.current_device())
    print(torch.cuda.get_device_name(0))

Eğer torch.cuda.is_available() True döndürürse, GPU’nuz başarıyla algılanmış demektir.

PyTorch Temelleri

PyTorch’un kalbinde “Tensor”lar yatar. Tensor’lar, çok boyutlu dizilerdir ve NumPy dizilerine benzerler, ancak GPU üzerinde çalışabilme yetenekleri ve otomatik türevlendirme desteği gibi ek özelliklere sahiptirler.

Tensor’lar: Tanımı ve Oluşturma

Tensor, PyTorch’taki temel veri yapısıdır. Skaler (0 boyutlu tensor), vektör (1 boyutlu tensor), matris (2 boyutlu tensor) veya daha yüksek boyutlu dizileri temsil edebilir.

Tensor Oluşturma Yöntemleri
import torch

Doğrudan veriden tensor oluşturma

data = [[1, 2], [3, 4]] x_data = torch.tensor(data) print(f"Doğrudan veriden: \n{x_data}\n")

NumPy dizisinden tensor oluşturma

import numpy as np np_array = np.array(data) x_np = torch.from_numpy(np_array) print(f"NumPy'dan: \n{x_np}\n")

Başka bir tensordan oluşturma (özelliklerini miras alır)

x_ones = torch.ones_like(x_data) # x_data ile aynı şekil ve veri tipi print(f"Birlerden oluşan: \n{x_ones}\n") x_rand = torch.rand_like(x_data, dtype=torch.float) # x_data ile aynı şekil, farklı veri tipi print(f"Rastgele sayılardan oluşan: \n{x_rand}\n")

Belirli şekillerde tensor oluşturma

shape = (2, 3,) ones_tensor = torch.ones(shape) zeros_tensor = torch.zeros(shape) rand_tensor = torch.rand(shape) print(f"Birler tensörü: \n{ones_tensor}\n") print(f"Sıfırlar tensörü: \n{zeros_tensor}\n") print(f"Rastgele tensör: \n{rand_tensor}\n")
Tensor Özellikleri

Bir tensor’un şeklini, veri tipini ve depolandığı cihazı (CPU veya GPU) sorgulayabilirsiniz:

tensor = torch.rand(3, 4)
print(f"Tensor'un şekli: {tensor.shape}")
print(f"Tensor'un veri tipi: {tensor.dtype}")
print(f"Tensor'un depolandığı cihaz: {tensor.device}")

Tensor İşlemleri

Tensor’lar üzerinde birçok aritmetik, mantıksal ve şekil değiştirme işlemi yapabilirsiniz.

Aritmetik İşlemler

Tensor’lar üzerinde toplama, çıkarma, çarpma, bölme gibi temel aritmetik işlemler doğrudan uygulanabilir.

tensor = torch.ones(4, 4)
tensor[:,1] = 0 # İkinci sütunu sıfır yap
print(f"Orijinal tensor:\n{tensor}\n")

İki tensor'u toplama

t1 = torch.ones(4, 4) t2 = torch.rand(4, 4) print(f"t1 + t2:\n{t1 + t2}\n")

Matris çarpımı

tensor_A = torch.tensor([[1, 2], [3, 4]]) tensor_B = torch.tensor([[5, 6], [7, 8]]) result_matmul = tensor_A.matmul(tensor_B) # veya tensor_A @ tensor_B print(f"Matris çarpımı:\n{result_matmul}\n")

Element-wise çarpım

result_mul = tensor_A * tensor_B print(f"Element-wise çarpım:\n{result_mul}\n")
İndeksleme ve Dilimleme

NumPy’daki gibi, tensor’ları indeksleyebilir ve dilimleyebilirsiniz.

tensor = torch.tensor([[1, 2, 3], [4, 5, 6], [7, 8, 9]])
print(f"İlk satır: {tensor[0]}\n")
print(f"İlk sütun: {tensor[:, 0]}\n")
print(f"İkinci satır, ikinci eleman: {tensor[1, 1]}\n")
print(f"Son satır: {tensor[-1]}\n")
Şekil Değiştirme

Bir tensor’un şeklini view() veya reshape() metotlarıyla değiştirebilirsiniz.

tensor = torch.arange(1, 10) # [1, 2, ..., 9]
print(f"Orijinal tensor: {tensor} (Şekil: {tensor.shape})\n")

reshaped_tensor = tensor.reshape(3, 3)
print(f"Yeniden şekillendirilmiş tensor:\n{reshaped_tensor} (Şekil: {reshaped_tensor.shape})\n")

-1 kullanarak otomatik boyut çıkarımı

reshaped_auto = tensor.reshape(-1, 3) # 3 sütun olacak şekilde satır sayısını otomatik ayarla print(f"Otomatik boyut çıkarımı ile:\n{reshaped_auto} (Şekil: {reshaped_auto.shape})\n")

GPU Hızlandırma

PyTorch’un en güçlü özelliklerinden biri, hesaplamaları GPU’ya taşıyarak hızlandırma yeteneğidir.

if torch.cuda.is_available():
    device = "cuda"
else:
    device = "cpu"
print(f"Kullanılan cihaz: {device}\n")

tensor = torch.ones(5, 5)
print(f"CPU üzerindeki tensor:\n{tensor}\n")

tensor_on_gpu = tensor.to(device)
print(f"GPU üzerindeki tensor:\n{tensor_on_gpu}\n")

GPU üzerinde işlem yapma

result_gpu = tensor_on_gpu * tensor_on_gpu print(f"GPU üzerinde yapılan çarpım:\n{result_gpu}\n")

Unutmayın, farklı cihazlardaki tensor’lar arasında doğrudan işlem yapamazsınız. Her iki tensor’un da aynı cihazda olması gerekir.

Otomatik Türevlendirme (Autograd)

Derin öğrenmede, modellerin ağırlıklarını optimize etmek için gradyan inişi gibi algoritmalar kullanılır. Bu algoritmalar, kayıp fonksiyonunun model parametrelerine göre türevlerini (gradyanlarını) hesaplamayı gerektirir. PyTorch’un autograd motoru, bu karmaşık türev hesaplamalarını otomatik olarak yapar.

Bir tensor’un gradyanlarını hesaplanabilir hale getirmek için requires_grad=True özelliğini ayarlamanız gerekir.

x = torch.ones(5, requires_grad=True)
y = x + 2
z = y  y  3
out = z.mean()

print(f"x: {x}")
print(f"y: {y}")
print(f"z: {z}")
print(f"out: {out}\n")

out.backward() # Geri yayılımı başlatır, x'in gradyanları hesaplanır

print(f"x'in gradyanları: {x.grad}")

Bu örnekte, out‘un x‘e göre türevi otomatik olarak hesaplanır ve x.grad özelliğinde saklanır. Bu, nöral ağların eğitiminde kritik bir adımdır.

Basit Bir Nöral Ağ Oluşturma

Şimdi PyTorch’un temel yapı taşlarını kullanarak basit bir nöral ağ oluşturalım ve eğitelim. Bir regresyon problemi için sentetik veri üzerinde çalışan küçük bir ağ tanımlayacağız.

Veri Hazırlığı

Öncelikle, modelimizi eğitmek için basit, sentetik bir veri seti oluşturalım.

import torch
import torch.nn as nn
import torch.optim as optim
import matplotlib.pyplot as plt

Sentetik veri oluşturma

y = 2x + 1 + gürültü

X = torch.randn(100, 1) * 10 y = 2 X + 1 + torch.randn(100, 1) 2

Veriyi görselleştirme

plt.figure(figsize=(8, 6)) plt.scatter(X.numpy(), y.numpy(), label='Sentetik Veri') plt.title('Sentetik Regresyon Veri Seti') plt.xlabel('X') plt.ylabel('y') plt.legend() plt.grid(True) plt.show()

Bu kod, 100 adet veri noktası içeren basit bir lineer ilişki (y = 2x + 1) ve biraz gürültü ile sentetik bir veri seti oluşturur.

Model Tanımlama

PyTorch’ta nöral ağlar, torch.nn.Module sınıfından türetilerek tanımlanır. Bu sınıf, modelin katmanlarını ve ileri yayılım mantığını içerir.

class SimpleLinearRegression(nn.Module):
    def __init__(self):
        super(SimpleLinearRegression, self).__init__()
        # Tek bir giriş özelliği (X) ve tek bir çıkış özelliği (y) için lineer katman
        self.linear = nn.Linear(1, 1)

    def forward(self, x):
        # İleri yayılımda verinin katmanlardan nasıl geçeceğini tanımlarız
        return self.linear(x)

Modeli başlatma

model = SimpleLinearRegression() print(model)

nn.Linear(in_features, out_features) katmanı, giriş ve çıkış boyutlarını belirtir. forward metodu, modelin giriş verisini alıp çıktı üretme sürecini tanımlar.

Kayıp Fonksiyonu ve Optimizatör

Modeli eğitmek için bir kayıp fonksiyonuna (loss function) ve bir optimizatöre ihtiyacımız var.

  • Kayıp Fonksiyonu: Modelin tahminlerinin gerçek değerlerden ne kadar saptığını ölçer. Regresyon problemleri için Genellikle nn.MSELoss (Mean Squared Error Loss) kullanılır.
  • Optimizatör: Modelin parametrelerini (ağırlıklar ve bias’lar) kayıp fonksiyonunu minimize edecek şekilde günceller. torch.optim modülü SGD, Adam, RMSprop gibi birçok optimizör içerir.
# Kayıp fonksiyonu: Ortalama Kare Hata (Mean Squared Error)
criterion = nn.MSELoss()

Optimizatör: Stokastik Gradyan İnişi (SGD)

model.parameters() ile modelin öğrenilebilir parametrelerini optimizatöre veririz

optimizer = optim.SGD(model.parameters(), lr=0.01) # lr: öğrenme oranı (learning rate)

Eğitim Döngüsü

Modeli eğitmek, veriyi ağdan geçirmek, kayıp hesaplamak, gradyanları geri yaymak ve ağırlıkları güncellemek adımlarını içeren bir döngüdür.

num_epochs = 100 # Eğitim döngüsü sayısı

Modeli GPU'ya taşıma (eğer varsa)

device = torch.device("cuda" if torch.cuda.is_available() else "cpu") model.to(device) X = X.to(device) y = y.to(device) loss_history = [] for epoch in range(num_epochs): # 1. İleri yayılım (Forward pass): Tahminleri hesapla outputs = model(X) # 2. Kaybı hesapla loss = criterion(outputs, y) # 3. Geri yayılım (Backward pass): Gradyanları sıfırla, gradyanları hesapla optimizer.zero_grad() # Önceki iterasyonlardan kalan gradyanları sıfırla loss.backward() # Kaybın model parametrelerine göre gradyanlarını hesapla # 4. Ağırlıkları güncelle optimizer.step() # Optimizatör kullanarak parametreleri güncelle loss_history.append(loss.item()) if (epoch + 1) % 10 == 0: print(f'Epoch [{epoch+1}/{num_epochs}], Loss: {loss.item():.4f}')

Eğitim sonrası modeli CPU'ya geri taşı (gerekirse)

model.to("cpu") X = X.to("cpu") y = y.to("cpu")

Eğitim kaybını görselleştirme

plt.figure(figsize=(8, 6)) plt.plot(range(num_epochs), loss_history, label='Eğitim Kaybı') plt.title('Eğitim Kaybının Epochlara Göre Değişimi') plt.xlabel('Epoch') plt.ylabel('Kayıp (MSE)') plt.legend() plt.grid(True) plt.show()

Bu döngüde, her epoch’ta:
1. Model, giriş verisi X üzerinde tahminler (outputs) yapar.
2. Hesaplanan tahminler ile gerçek değerler y arasındaki kayıp criterion kullanılarak hesaplanır.
3. optimizer.zero_grad() ile önceki iterasyonlardan kalan gradyanlar temizlenir.
4. loss.backward() çağrısı, kayıp fonksiyonunun modelin öğrenilebilir parametrelerine göre gradyanlarını hesaplar.
5. optimizer.step() çağrısı, hesaplanan gradyanları kullanarak modelin parametrelerini günceller.

Modeli Kaydetme ve Yükleme

Eğitilmiş bir modeli daha sonra kullanmak veya paylaşmak için kaydetmek önemlidir.

# Modeli kaydetme
torch.save(model.state_dict(), 'simple_linear_model.pth')
print("Model başarıyla kaydedildi.")

Modeli yükleme

Önce boş bir model örneği oluşturmalısınız

loaded_model = SimpleLinearRegression() loaded_model.load_state_dict(torch.load('simple_linear_model.pth')) loaded_model.eval() # Çıkarım moduna geç (dropout, batch norm gibi katmanlar için önemli) print("Model başarıyla yüklendi.")

Yüklenen model ile tahmin yapma

with torch.no_grad(): # Gradyan hesaplamayı devre dışı bırak test_X = torch.tensor([[15.0], [-5.0]]) predictions = loaded_model(test_X) print(f"Test X: {test_X.numpy().flatten()}, Tahminler: {predictions.numpy().flatten()}")

model.state_dict() metodu, modelin öğrenilebilir parametrelerini içeren bir sözlük döndürür. Bu sözlük, torch.save() ile disk’e kaydedilir. Yükleme yaparken, önce modelin mimarisini tanımlayan boş bir model örneği oluşturulmalı, ardından load_state_dict() ile kaydedilen parametreler yüklenmelidir. model.eval() çağrısı, modeli çıkarım (inference) moduna geçirir; bu, eğitim sırasında aktif olan bazı katmanların (örneğin dropout) davranışını değiştirir. with torch.no_grad(): bloğu, çıkarım sırasında gereksiz gradyan hesaplamalarını devre dışı bırakarak bellek kullanımını ve hızı optimize eder.

Daha İleri Konulara Kısa Bir Bakış

PyTorch’un temelini anladıktan sonra, daha karmaşık ve gerçek dünya uygulamalarına yönelik konulara geçiş yapabilirsiniz.

Veri Yükleyiciler (torch.utils.data)

Gerçek dünya veri kümeleri genellikle çok büyük olur ve belleğe sığmaz. torch.utils.data modülü, veri kümelerini yönetmek ve partiler (batches) halinde yüklemek için güçlü araçlar sunar:

  • Dataset: Veri örneklerini ve etiketlerini temsil eden bir soyut sınıftır. Kendi özel veri setlerinizi oluşturmak için bu sınıfı türetebilirsiniz.
  • DataLoader: Dataset‘ten verileri partiler halinde yükler, karıştırır (shuffle), paralel yükleme için çoklu işlem kullanabilir.
from torch.utils.data import Dataset, DataLoader

class CustomDataset(Dataset):
    def __init__(self, X, y):
        self.X = X
        self.y = y

    def __len__(self):
        return len(self.X)

    def __getitem__(self, idx):
        return self.X[idx], self.y[idx]

Örnek kullanım

dataset = CustomDataset(X, y) dataloader = DataLoader(dataset, batch_size=32, shuffle=True) for batch_X, batch_y in dataloader: # Her bir batch üzerinde eğitim yapabilirsiniz # print(batch_X.shape, batch_y.shape) pass

Önceden Eğitilmiş Modeller (torchvision.models)

Bilgisayar görüşü alanında, ImageNet gibi büyük veri kümeleri üzerinde eğitilmiş güçlü modeller (ResNet, VGG, AlexNet vb.) mevcuttur. torchvision.models modülü, bu modelleri kolayca yüklemenizi ve transfer öğrenimi için kullanmanızı sağlar.

import torchvision.models as models

Önceden eğitilmiş bir ResNet-18 modelini yükle

resnet18 = models.resnet18(pretrained=True) print(resnet18)

Son katmanı kendi problemimize göre değiştirme (transfer öğrenimi için)

num_ftrs = resnet18.fc.in_features resnet18.fc = nn.Linear(num_ftrs, num_classes) # num_classes kendi sınıf sayınız

Transfer Öğrenimi

Önceden eğitilmiş modelleri kullanarak, az veriyle bile yeni görevler için yüksek performanslı modeller geliştirebilirsiniz. Bu teknik, modelin genel özellikleri öğrenmiş katmanlarını dondurup (eğitmemek) sadece son katmanlarını kendi veriniz üzerinde eğitmeyi içerir.

Dağıtık Eğitim

Büyük modelleri ve veri kümelerini eğitmek için birden fazla GPU veya birden fazla makine kullanmak gerekebilir. PyTorch, torch.distributed modülü ile dağıtık eğitimi destekler, bu da eğitim sürecini paralel hale getirerek hızlandırır.

Sonuç

PyTorch, esnek, Pythonic ve güçlü bir derin öğrenme kütüphanesidir. Dinamik hesaplama grafikleri, sezgisel API’si ve güçlü GPU hızlandırma desteği sayesinde, hem araştırmacılar hem de geliştiriciler için derin öğrenme projelerini hayata geçirmeyi kolaylaştırır. Bu makalede, PyTorch’u nasıl kuracağınızı, tensor’lar gibi temel yapı taşlarını nasıl kullanacağınızı, otomatik türevlendirme mekanizmasını ve basit bir nöral ağı nasıl oluşturup eğiteceğinizi ayrıntılı bir şekilde ele aldık. Ayrıca, veri yükleyiciler ve önceden eğitilmiş modeller gibi daha ileri konulara da kısaca değindik.

PyTorch’un modüler yapısı, karmaşık model mimarilerini denemenize ve kendi özel katmanlarınızı kolayca tanımlamanıza olanak tanır. Geniş ve aktif bir topluluğa sahip olması, karşılaşabileceğiniz sorunlara hızlıca çözüm bulmanızı sağlar. Derin öğrenme dünyasına adım atmak veya mevcut projelerinizi daha verimli hale getirmek istiyorsanız, PyTorch öğrenmeye değer bir yatırımdır.

Öğrenme yolculuğunuza devam ederken, PyTorch’un resmi belgelerini, online dersleri ve GitHub’daki örnek projeleri keşfetmeniz şiddetle tavsiye edilir. Pratik yaparak ve farklı model mimarileriyle deneyler yaparak PyTorch’taki yetkinliğinizi artırabilirsiniz. Derin öğrenmenin heyecan verici dünyasında PyTorch ile başarılı projelere imza atmanız dileğiyle!

Yorumlar
İçeriği beğendiniz mi? Bir tartışma başlatın veya görüşlerinizi paylaşın.
Yorum Yaz

Bir yanıt yazın

E-posta adresiniz yayınlanmayacak. Gerekli alanlar * ile işaretlenmişlerdir

Gönder

E-posta Bülteni
Yazılım Topluluğuna Katılın
En son güncellemeleri, yaratıcı ipuçlarını ve özel kaynakları doğrudan e-posta kutunuza alın. Tasarım ve inovasyonun geleceğini birlikte keşfedelim.
Mobil sürümden çık