Gece Modu

Variational Autoencoders yöntemi ile random sayılardan cartoon yüzler üreten bir derin öğrenme modeli eğiteceğiz.

Bu yazı, Evrim Ağacı'na ait, özgün bir içeriktir. Konu akışı, anlatım ve detaylar, Evrim Ağacı yazarı/yazarları tarafından hazırlanmış ve/veya derlenmiştir. Bu içerik için kullanılan kaynaklar, yazının sonunda gösterilmiştir. Bu içerik, diğer tüm içeriklerimiz gibi, İçerik Kullanım İzinleri'ne tabidir.

Projenin Amacı

Bu yazıda sizlere bir makine öğrenmesi projesini aşama aşama, kodları ile birlikte açıklamaya çalışacağız. Projenin sonunda, tamamen rastgele üretilen ("random") sayıları kullanarak, farklı çizgi yüzleri (İng: "cartoon faces") üreten bir derin öğrenme modeline sahip olacaksınız. Bu modelin ürettiği sonuçları aşağıdaki görselde görebilirsiniz.

Derin Öğrenme Modeli Sonuçları
Derin Öğrenme Modeli Sonuçları

Temel Bilgi Gereksinimleri

Özellikle belirtmek isteriz ki bu yazıda yer alan kodlar başlangıç seviye yazılımcılar ve Makine Öğrenmesi hakkında en azından temel seviye bilgisi olmayanlar için uygun olmayabilir. Bunun için sizlere Stanford Üniversitesi Profesörü Andrew Ng tarafından hazırlanmış, tamamen ücretsiz bir şekilde alınabilen bu Coursera dersini tavsiye ederiz. Ayrıca kurs, Türkçe alt yazılı olduğu için İngilizce bilmiyorsanız bile takip edebilirsiniz.

Projenin Temelleri

Projenin GitHub sayfasına buradan ulaşabilirsiniz. Ayrıca projede kullanacağımız yazılım ve kütüphaneler şu şekilde olacak:

  • TensorFlow 2.0.0b1
  • Python 3.6
  • numpy
  • pandas
TensorFlow Ne Yapar?
TensorFlow Ne Yapar?

Projenin Basamakları

1. Veri Seti İhtiyacı

İlk problemimiz, bir veri seti bulmak. Amacımız çizgi karakterler oluşturan bir derin öğrenme modeli yapmak olduğu için, ihtiyacımız olan şey de binlerce çizgi yüz. Bunun için küçük bir arama yaparsanız, Google tarafından yayınlanmış bu veris setini bulabilirsiniz. Bu veri setinde "etiketlenmiş" (yani saç rengi, göz rengi, ten rengi vs. açısından kategorize edilmiş) yüzler bulunmaktadır. Eğer ilgili olanlar varsa, bir üst seviye olan StyleGAN algoritmasını kullanarak daha keskin ve başarılı sonuçlar elde edebilirler.

Veri seti yaklaşık olarak 4.45 GB, ayrıca etiketlenen özellikleri indirme sayfasının en altında da bulabilirsiniz.

Eğer veri setini indirip incelediyseniz, göreceksiniz ki her bir yüz için bir ".png" bir de ".csv" dosyası var. .png dosyasında yüz resmi, .csv dosyasında ise etiketlenmiş veriler bulunuyor.

2. Veri Setini İşlemek ve Veri Hattı ("Input Pipeline") Oluşturmak

Bu basamakta TensorFlow'un kendi "tf.data" özelliğini kullanacağız. Bu özellik sayesinde verilerimiz bilgisayardan (HDD'den veya SDD'den) okunurken, aynı zamanda GPU (Graphics Processing Unit) modelin eğitimi için gerekli olan matematiksel işlemleri yapacak. Bu da, zaman kaybı olmayacağı anlamına geliyor.

Python ve TensorFlow 2.0 ile bunu uygulamak için, gerekli kütüphaneleri "import" ediyoruz:

import tensorflow as tf
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt

from glob import glob
from tqdm import tqdm
tf.data çalışma mantığı
tf.data çalışma mantığı

Daha sonra veri işleme konusunda işlemleri yapacak bir sınıf oluşturuyoruz. Burada "sınıf temelli" bir programlama yapmayı daha çok tercih ediyoruz; siz eğer isterseniz daha farklı da yazabilirsiniz. Ayrıca sınıfımızın adını "Marshall" koyacağız. Bunun tek sebebi var: Programlama yaparken sınıflarımıza sevdiğimiz dizi karakterlerinin adını vermekten hoşlanıyor olmamız. Siz istediğiniz ismi seçebilirsiniz.

class Marshall:
def load_image(self, x):
img = tf.io.read_file(x)
img = tf.image.decode_png(img, channels=3)
if self.central_crop:
img = tf.image.central_crop(img, 0.825)
img = tf.image.resize(img, (self.xs, self.ys), method="nearest")

if self.random_flip:
img = tf.image.random_flip_left_right(img)

return tf.cast(img, tf.float32) / 255.

def __init__(self, main_path: str, image_shape: tuple, random_flip: bool = True, central_crop: bool = False):
self.main_path = main_path
self.xs, self.ys, self.channels = image_shape
self.random_flip = random_flip
self.central_crop = central_crop

self.x_data = self.read_all_path()
self.x_data = tf.convert_to_tensor(self.x_data)

self.dataset = tf.data.Dataset.from_tensor_slices(self.x_data).shuffle(len(self.x_data))
self.dataset = self.dataset.map(self.load_image)
self.dataset = self.dataset.prefetch(buffer_size=tf.data.experimental.AUTOTUNE)

def read_all_path(self):
paths = []

for path in tqdm(glob(f"{self.main_path.rstrip('/')}/*/*.png")):
paths.append(path)

paths = np.array(paths)

return paths

Küçük bir not: "__init__", sınıf çağrıldığında ilk çalışan fonksiyondur.

Bu sınıf ne yapıyor dersiniz? İlk başta, bazı değerleri "self" değişkenine atıyor, daha sonra "read_all_path" fonksiyonu ile bütün png dosyalarının adresleri bir listeye kaydediliyor. En son olarak bu liste bir tf.data objesine dönüştürülüyor ve ".map(self.load_image)" ifadesi ile listedeki her bir elemanın eğitilmeden önce bu fonksiyondan geçirilmesini sağlıyoruz. Çünkü biz listeye verileri resmin adresi şeklinde kaydettik, ihtiyacımız olan şey ise resmin kendisi... Bu fonksiyon da bunu yapıyor: Resmi okuyup, sayılara dönüştürüyor.

Kod yazma tarzımız, okuyucuya farklı gelmiş olabilir. Kafanızın karıştığı yerler olabilir. Bu, kodu yazandan yazana çok değişecektir. Ama kısaca şöyle düşünün: Bu sınıf, tüm resim adreslerini alıyor, bu resimleri okuyor ve model eğitimi için kullanılabilir bir hale getiriyor.

Aranızda neden resim adresi okumak yerine en başta sadece resmi okumadık diye düşünenler olabilir, onu da şöyle açıklayalım: Eğer bütün resimleri okuyup bir listeye atasaydık, RAM'imiz bunların hepsini depolayamazdı. Tabii eğer bilgisayarınızın RAM'i çok yüksek ise bu bir sorun olmayabilir. Bu sorunla karşılaşmamak için, yazdığımız kod sadece belli sayıda resmi okuyor, onları işliyor, RAM'den siliyor ve daha sonra yeni resimler ile döngüyü tekrar ediyor. Böylece parça parça ve "RAM'imizi kasmadan" veriyi işleyebiliyoruz.

3. Derin Öğrenme Modeli Oluşturmak

Sıfırdan görüntü üretimi ile ilgili birçok farklı algoritma var; dolayısıyla yapmak istediğiniz işe göre tercihler değişiklik gösterebilir. Biz bu iş için "variational autoencoders" tekniğini kullanacağız.

Bu tekniğin detaylarını açıklamak için işin matematiğine inmek gerekiyor ve bu yazının amacını fazlasıyla aşar; belki ileride bu detaylara da girebiliriz. Eğer yapay zeka uygulamalarının temel matematiğini biliyorsanız, bu konuda bolca ve çok öğretici kaynaklar bulabilirsiniz. Eğer yeni başlayan bir kişiyseniz, işin matematiğine girip kafanızı allak bullak etmek istemeyiz; çünkü bunun için daha temel örnekler üzerinden anlatım yapılması daha doğru olur. Bu yöntemin ileri düzeyde teknik kısmı hem yazının başında önerdiğimiz Coursera dersinde, hem de TensorFlow'un buradaki yazısında anlatılıyor.

Şimdi bu modeli barındıran sınıfı tanımlayacağız. Bu defa sınıf ismimiz "Barney". Nedenini biliyorsunuz. Başlayalım:

class Barney(tf.keras.Model):
def compute_output_signature(self, input_signature):
pass

def encoder_model(self, activation_function: tf.nn = tf.nn.elu, last_layer_activation: tf.nn = None):
input_layer = tf.keras.layers.Input(shape=(self.xs, self.ys, self.channels), name="input_1")

x = tf.keras.layers.Conv2D(32, (3, 3), strides=1, activation=activation_function,
kernel_regularizer=tf.keras.regularizers.l2())(input_layer)
x = tf.keras.layers.Dropout(0.25)(x)
x = tf.keras.layers.Conv2D(64, (3, 3), strides=2, activation=activation_function,
kernel_regularizer=tf.keras.regularizers.l2())(x)
x = tf.keras.layers.Conv2D(64, (3, 3), strides=1, activation=activation_function,
kernel_regularizer=tf.keras.regularizers.l2())(x)
x = tf.keras.layers.MaxPooling2D((3, 3), strides=2)(x)

x = tf.keras.layers.Conv2D(128, (3, 3), strides=1, activation=activation_function,
kernel_regularizer=tf.keras.regularizers.l2())(x)
x = tf.keras.layers.Dropout(0.25)(x)
x = tf.keras.layers.Conv2D(256, (3, 3), strides=2, activation=activation_function,
kernel_regularizer=tf.keras.regularizers.l2())(x)
x = tf.keras.layers.Conv2D(256, (3, 3), strides=1, activation=activation_function,
kernel_regularizer=tf.keras.regularizers.l2())(x)
x = tf.keras.layers.MaxPooling2D((3, 3), strides=2)(x)

x = tf.keras.layers.Conv2D(256, (3, 3), strides=1, activation=activation_function,
kernel_regularizer=tf.keras.regularizers.l2())(x)
x = tf.keras.layers.Dropout(0.25)(x)
x = tf.keras.layers.Conv2D(512, (3, 3), strides=1, activation=activation_function,
kernel_regularizer=tf.keras.regularizers.l2())(x)

x = tf.keras.layers.Flatten()(x)
x = tf.keras.layers.Dense(self.last_layer_units*2, activation=last_layer_activation,
kernel_regularizer=tf.keras.regularizers.l2())(x)

model = tf.keras.models.Model(input_layer, x)
model.summary()

return model

def decoder_model(self, activation_function: tf.nn = tf.nn.elu, last_layer_activation: tf.nn = None):
input_layer = tf.keras.layers.Input(
shape=(self.last_layer_units, ), name="input_2"
)

x = tf.keras.layers.Dense(512, activation=activation_function,
kernel_regularizer=tf.keras.regularizers.l2())(input_layer)
x = tf.keras.layers.Reshape((1, 1, 512))(x)

x = tf.keras.layers.Conv2DTranspose(512, (3, 3), strides=2, activation=activation_function,
kernel_regularizer=tf.keras.regularizers.l2())(x)
x = tf.keras.layers.UpSampling2D()(x)
x = tf.keras.layers.Conv2DTranspose(256, (3, 3), strides=1, activation=activation_function,
kernel_regularizer=tf.keras.regularizers.l2())(x)
x = tf.keras.layers.Conv2DTranspose(256, (3, 3), strides=2, activation=activation_function, padding="same",
kernel_regularizer=tf.keras.regularizers.l2())(x)
x = tf.keras.layers.Dropout(0.25)(x)
x = tf.keras.layers.Conv2DTranspose(128, (3, 3), strides=2, activation=activation_function, padding="same",
kernel_regularizer=tf.keras.regularizers.l2())(x)
x = tf.keras.layers.UpSampling2D()(x)
x = tf.keras.layers.Conv2DTranspose(64, (3, 3), strides=1, activation=activation_function, padding="same",
kernel_regularizer=tf.keras.regularizers.l2())(x)
x = tf.keras.layers.Conv2DTranspose(64, (3, 3), strides=1, activation=activation_function, padding="same",
kernel_regularizer=tf.keras.regularizers.l2())(x)
x = tf.keras.layers.Dropout(0.25)(x)
x = tf.keras.layers.Conv2DTranspose(32, (3, 3), strides=2, activation=activation_function, padding="same",
kernel_regularizer=tf.keras.regularizers.l2())(x)
x = tf.keras.layers.Conv2DTranspose(self.channels, (3, 3), strides=1, activation=last_layer_activation,
padding="same")(x)

model = tf.keras.models.Model(input_layer, x)
model.summary()

return model

def save_models(self):
self.encoder.save(self.file_path.replace(".h5", "_encoder.h5"))
self.decoder.save(self.file_path.replace(".h5", "_decoder.h5"))

def __init__(self, image_shape: tuple, file_path: str, last_layer_units: int = 1024, lr: float = 0.001):
super(Barney, self).__init__()
self.xs, self.ys, self.channels = image_shape
self.file_path = file_path

self.last_layer_units, self.lr = last_layer_units, lr

tf.compat.v1.gfile.MakeDirs("".join(self.file_path.split("/")[:-1]))

try:
self.encoder = tf.keras.models.load_model(self.file_path.replace(".h5", "_encoder.h5"),
custom_objects={"leaky_relu": tf.nn.leaky_relu})
except OSError:
self.encoder = self.encoder_model(
activation_function=tf.nn.leaky_relu,
last_layer_activation=None
)

try:
self.decoder = tf.keras.models.load_model(self.file_path.replace(".h5", "_decoder.h5"),
custom_objects={"leaky_relu": tf.nn.leaky_relu})
except OSError:
self.decoder = self.decoder_model(
activation_function=tf.nn.leaky_relu,
last_layer_activation=None
)

self.optimizer = tf.keras.optimizers.Adam(self.lr, beta_1=0.5)

def encode(self, x: tf.Tensor):
mean, logvar = tf.split(self.encoder(x, training=True), num_or_size_splits=2, axis=1)
return mean, logvar

def decode(self, z: tf.Tensor, apply_sigmoid: bool = False):
logits = self.decoder(z, training=True)
if apply_sigmoid:
logits = tf.sigmoid(logits)

return logits

@staticmethod
def reparameterize(mean: tf.Tensor, logvar: float):
return tf.random.normal(shape=mean.shape) * tf.exp(logvar * .5) + mean

def generate_sample(self, eps: tf.Tensor):
return self.decode(eps, apply_sigmoid=True)

@staticmethod
def log_normal_pdf(sample: float, mean: float, logvar: float, raxis: float = 1):
return tf.reduce_sum(-.5 * ((sample - mean) ** 2. * tf.exp(-logvar) + logvar + 1.837877), axis=raxis)

@tf.function
def compute_loss(self, x: tf.Tensor):
mean, logvar = self.encode(x)
z = self.reparameterize(mean, logvar)
x_logit = self.decode(z)

cross_ent = tf.nn.sigmoid_cross_entropy_with_logits(logits=x_logit, labels=x)
logpx_z = -tf.reduce_sum(cross_ent, axis=[1, 2, 3])
logpz = self.log_normal_pdf(z, 0., 0.)
logqz_x = self.log_normal_pdf(z, mean, logvar)

return -tf.reduce_mean(logpx_z + logpz - logqz_x), x_logit

@tf.function
def train_step(self, x: tf.Tensor):
with tf.GradientTape() as tape:
loss, outputs = self.compute_loss(x)

gradients = tape.gradient(loss, self.trainable_variables)
self.optimizer.apply_gradients(zip(gradients, self.trainable_variables))

return loss, outputs

Bu sınıfta epey bir fonksiyon olduğunu görebilirsiniz. Örnek olması açısından sadece "train_step" fonksiyonunu açıklayacağız, çünkü diğer fonksiyonların arkasında çok büyük ve büyüleyici bir matematik yatıyor. Yukarıda sözünü ettiğimiz nedenlerle bunun detaylarını tek bir makaleye sığdırmak imkansız ve çok yorucu olurdu.

"train_step" fonksiyonu, "loss" dediğimiz ve ne anlama geldiğini birazdan açıklayacağımız değeri hesaplıyor ve tüm modeldeki sayısal değerleri bu loss (kayıp) değerine göre yeniden düzenliyor ve optimize ediyor. Bu işin arka planında da yine büyüleyici bir matematik var. Coursera dersini alarak bunu öğrenebilirsiniz.

Peki nedir bu "loss" değeri? Çok basitleştirilmiş bir şekilde anlatacak olursak: Diyelim ki bir resmin kedi veya köpek olduğunu söyleyen bir makine öğrenmesi projesi yapıyorsunuz. Modeli oluşturdunuz ve bu model 0-1 arasında bir sayı değeri veriyor ("output ediyor"). 0 kedi, 1 de köpek demek. Senaryomuzda, model "0.2" tahmin ediyor, yani kedi. Doğrusu da kedi, yani 0. Loss burada 0.2 oluyor, çünkü gerçek değer ile tahmin edilen değer arasında 0.2'lik bir fark var. Tabii bu loss fonksiyonları çok daha ileri matematikle yapılıyor ve çok fazla türü var: Merak edenler MSE, MAE, Binary, Sparse Categorical Crossentropy vb. algoritmalara göz atabilirler.

İlgilisine ek not düşecek olursak: Model, sonuç değerini 0 ve 1 arasına sıkıştırmak için sigmoid fonksiyonu denen bir fonksiyon kullanıyor ve aktivasyon fonksiyonları kullanıyor. Bu fonksiyonlar modeldeki sayısal değerleri girdi ("input") alarak bir çıktı ("output") üretiyor ve eski sayısal değerler bu yeni çıktılar ile döngüye devam ediyor. Fonksiyonlar ve grafikleri:

Aktivasyon Fonksiyonları
Aktivasyon Fonksiyonları

Ve yine ilgilisine ek bir diğer not düşersek: Sonucu resim olarak almak istediğimizde de "sigmoid" aktivasyon fonksiyonu kullanıyoruz. Çünkü resimler 0-255 arasındaki sayı değerleridir ve makine öğrenmesinde resimler işlenirken genelde 255'e bölünür, bu da onları 0-1 arasına sıkıştırır.

4. Model Eğitimi

İşte bu son aşama. Burada, 3. aşamada oluşturduğumuz derin öğrenme modelini, 2. aşamada oluşturduğumuz veri hattından alacağımız verilerle eğiteceğiz.

Bunun için veriyi alacak, modele gönderecek, loss değerindeki değişimi ve üretilen resimleri kaydedecek/grafikleyecek bir sınıfa ihtiyacımız var. İsmi "Robin":

class Robin:
def __init__(self, marshall_pipeline: Marshall, barney_model: Barney, epochs: int = 10, batch_size: int = 32):
self.marshall_data = marshall_pipeline
self.barney_model = barney_model
self.batch_size, self.epochs = batch_size, epochs

self.file_writer = tf.summary.create_file_writer("graphs/")
self.marshall_data.dataset = self.marshall_data.dataset.batch(self.batch_size)

def save_images_to_tensorboard(self, epoch: int, real_ex: tf.Tensor, regenerated_ex: tf.Tensor):
samples_from_random = self.barney_model.generate_sample(tf.random.normal(
shape=(self.batch_size, self.barney_model.last_layer_units,)
))

with tf.device("/cpu:0"):
with self.file_writer.as_default():
tf.summary.image("real images", real_ex.numpy(), step=epoch, max_outputs=self.batch_size,
description="real images, no effect from Barney!")

tf.summary.image("regenerated images", regenerated_ex.numpy(), step=epoch, max_outputs=self.batch_size,
description="regenerated images, encoded and decoded by Barney!")

tf.summary.image("decoded images", samples_from_random.numpy(), step=epoch, max_outputs=self.batch_size,
description="decoded images, generated from random bottleneck, decoded by Barney!")

def train_model(self):
x = regenerated_images = None
q = int(tf.data.experimental.cardinality(self.marshall_data.dataset))

for epoch in range(self.epochs):
bar = tf.keras.utils.Progbar(target=q)

for i, x in enumerate(self.marshall_data.dataset):
loss_value, regenerated_images = self.barney_model.train_step(x=x)

with self.file_writer.as_default():
tf.summary.scalar("loss", loss_value, step=(q*epoch)+i, description="Barney's Loss")

bar.update(current=int(i+1), values=[["loss", loss_value]])

self.save_images_to_tensorboard(
epoch=epoch,
real_ex=x,
regenerated_ex=regenerated_images
)

self.barney_model.save_models()

def generate_random_images(self, number_of_images: int = 100):
samples_from_random = self.barney_model.generate_sample(tf.random.normal(
shape=(number_of_images, self.barney_model.last_layer_units,)
))

c = r = int(tf.sqrt(float(number_of_images)).numpy())
fig = plt.figure(figsize=(64, 64))

for i in range(int(c*r)):
fig.add_subplot(r, c, i+1)
plt.axis("off")
plt.imshow(samples_from_random[i])

plt.savefig("results.png")
plt.show()

Burada olan biteni kısaca açıklayacak olursak: Eğitimi, tahmin edildiği üzere "train_model" fonksiyonu yapıyor. Veri hattından ("Marshall" sınıfı ile tanımladığımızı hatırlayın) veriyi alıyor, daha sonra bunu modele veriyor, loss fonksiyonunu hesaplayıp sayısal değerleri düzenliyor (bunu da Barney sınıfı ile tanımladık).

Belki bu iki satır dikkatinizi çekmiş olabilir:

with self.file_writer.as_default():
tf.summary.scalar("loss", loss_value, step=(q*epoch)+i, description="Barney's Loss")

Burada yaptığımız, loss değerindeki değişimi bir grafiğe çizmek, bunu da yine TensorFlow ile gelen TensorBoard kütüphanesi ile yapıyoruz.

"save_images_to_tensorboard" fonksiyonu da gerçek resimleri ve random sayılarla üretilen resimleri TensorBoard'a kaydediyor.

Şimdi Sıra Eğitimde!

Başlayalım:

marshall = Marshall(
main_path="cartoonset100k",
image_shape=(128, 128, 3),
random_flip=False,
central_crop=True
)

barney = Barney(
image_shape=(128, 128, 3),
file_path="models/my_model.h5",
last_layer_units=1024,
lr=0.0001
)

robin = Robin(
marshall_pipeline=marshall,
barney_model=barney,
epochs=10,
batch_size=32,
)

robin.train_model()

Bu kod bloğunda, sırayla tüm sınıfları oluşuyor, en sonunda eğitici sınıfta birleşip eğitimi yapıyorlar.

Sonuç

Eğer makine öğrenmesi alanında tecrübeniz yoksa bu anlatımda boş kalan çok fazla yer olmuştur. Daha fazla temel bilgi için başta verdiğimiz kursa göz atmanızı önemle öneriyoruz.

Dünya değişiyor, biz değişiyoruz, makineler değişiyor. Bunun bir parçası olmak çok heyecan verici, herkesin tatması gereken bir şey bu. Ayrıca bu alan her yere yayılmakta. Canlıyı, DNA yapısını ve onunla ilgili her şeyi Yapay Zeka yardımı ile anlayıp, insan-bilgisayar arayüzleri üretmek isteyen çok sayıda start-up var. Fiziği, hatta tarihi bile AI ile anlamlandırmaya çalışan projeler mevcut!

Bu İçerik Size Ne Hissettirdi?
  • Muhteşem! 2
  • Tebrikler! 1
  • Bilim Budur! 1
  • Mmm... Çok sapyoseksüel! 0
  • Güldürdü 0
  • İnanılmaz 0
  • Umut Verici! 1
  • Merak Uyandırıcı! 1
  • Üzücü! 0
  • Grrr... *@$# 0
  • İğrenç! 0
  • Korkutucu! 0

Evrim Ağacı'na her ay sadece 1 kahve ısmarlayarak destek olmak ister misiniz?

Şu iki siteden birini kullanarak şimdi destek olabilirsiniz:

kreosus.com/evrimagaci | patreon.com/evrimagaci

Çıktı Bilgisi: Bu sayfa, Evrim Ağacı yazdırma aracı kullanılarak 18/11/2019 20:59:06 tarihinde oluşturulmuştur. Evrim Ağacı'ndaki içeriklerin tamamı, birden fazla editör tarafından, durmaksızın elden geçirilmekte, güncellenmekte ve geliştirilmektedir. Dolayısıyla bu çıktının alındığı tarihten sonra yapılan güncellemeleri görmek ve bu içeriğin en güncel halini okumak için lütfen şu adrese gidiniz: https://evrimagaci.org/s/7994

İçerik Kullanım İzinleri: Evrim Ağacı'ndaki yazılı içerikler orijinallerine hiçbir şekilde dokunulmadığı müddetçe izin alınmaksızın paylaşılabilir, kopyalanabilir, yapıştırılabilir, çoğaltılabilir, basılabilir, dağıtılabilir, yayılabilir, alıntılanabilir. Ancak bu içeriklerin hiçbiri izin alınmaksızın değiştirilemez ve değiştirilmiş halleri Evrim Ağacı'na aitmiş gibi sunulamaz. Benzer şekilde, içeriklerin hiçbiri, söz konusu içeriğin açıkça belirtilmiş yazarlarından ve Evrim Ağacı'ndan başkasına aitmiş gibi sunulamaz. Bu sayfa izin alınmaksızın düzenlenemez, Evrim Ağacı logosu, yazar/editör bilgileri ve içeriğin diğer kısımları izin alınmaksızın değiştirilemez veya kaldırılamaz.

Soru Sorun!
Reklam
Reklam
Öğrenmeye Devam Edin!
Evrim Ağacı %100 okur destekli bir bilim platformudur. Maddi destekte bulunarak Türkiye'de modern bilimin gelişmesine güç katmak ister misiniz?
Destek Ol
Gizle
Türkiye'deki bilimseverlerin buluşma noktasına hoşgeldiniz!

Göster

Şifremi unuttum Üyelik Aktivasyonu

Göster

Şifrenizi mi unuttunuz? Lütfen e-posta adresinizi giriniz. E-posta adresinize şifrenizi sıfırlamak için bir bağlantı gönderilecektir.

Geri dön

Eğer aktivasyon kodunu almadıysanız lütfen e-posta adresinizi giriniz. Üyeliğinizi aktive etmek için e-posta adresinize bir bağlantı gönderilecektir.

Geri dön

Close
“Eğer haklı olduğunu biliyorsan, başkalarının ne düşündüğünü umursamazsın. Er ya da geç gerçekler ortaya çıkar.”
Barbara McClintock
Geri Bildirim Gönder