← Back to list

Transfer Learning Metoduyla Öneri Motoru Geliştirmek

…benzer ürünleri tavsiye eden bir öneri motorunu Transfer Learning metodu ile yapmanın aslında ne kadar basit olduğunu göstereceğim.

M. Orhun Kose · 2024-04-10 11:23 · 133 claps · 9.5 min read
#recommendation-engine #transfer-learning #image-feature-extraction #resnet #deep-learning
Open on Medium ↗
Wiki topics: ML · Machine Learning EDU · Education & Learning

Generated by gencraft

Generated by gencraft

Transfer Learning Metoduyla Öneri Motoru Geliştirmek

Netflix’te film/dizi, YouTube’ta video, Instagram’da reels, Amazon’da ürün önerme olarak karşımıza çıkan ve devasa bir popülerliğe ulaşan öneri motorları artık neredeyse bütün e-ticaret sitelerinde farklı şekillerde bulunuyor. “Bu ürün şunlara benziyor bak”, “bunu alan bunu da aldı”, “sana çok benzeyen biri şuna bayıldı” gibi temel insan güdülerine hitap etmeye çalışan birçok metot mevcut. Bu yazıda ise size, benzer ürünleri tavsiye eden bir öneri motorunu, Transfer Learning metodu ile yapmanın aslında ne kadar basit olduğunu göstereceğim.

Her şeyden önce, benzer ürün ne demek?

Benzerlik, insana göre çok içgüdüsel bir kavram olsa da bilgisayar dilinde sayısal olarak bunu ifade etmek o kadar da basit değil. İnsan olarak iki görüntü arasındaki benzerliği analiz etmeye çalışırken, görüntülerin içeriğini tanımlayan temel özelliklere (renkler, şekiller, kenarlar, boyut…) odaklanırız. Derin öğrenme modelleri ise, bu önemli özellikleri, görüntülerden çıkarmayı öğrenerek bu yaklaşımı taklit eder.

Görsellerin ayırt edici özelliklerini çok boyutlu bir vektör olarak ifade etmeye “feature extraction” diyoruz. Derin öğrenme modellerinde girdi olarak verilen görseller, filtre gibi davranan bir dizi katmandan geçer. Bu filtreler görüntüdeki belirli desenleri, renkleri, kombinasyonları tespit eder. Görsel, daha derin katmanlardan geçtikçe filtreler daha karmaşık özellikleri tespit eder ve bu katmanların son çıktısı, bu bütün özellikleri temsil eden bir vektördür; bir bakıma görüntünün içeriğinin parmak izidir.

Görsellerin içeriğinin parmak izini çıkarttıktan sonra elimizde hala çözülmesi gereken büyük bir sorun var; bu parmak izleri birbirine ne kadar benziyor?

Çok boyutlu vektörlerin uzayda farklı yöntemlerle uzaklıklarını karşılaştırabiliyoruz. Görüntülerin parmak izi olarak ifade ettiğimiz iki farklı görsel özellik vektörünün ne kadar benzediğinin sorusunun cevabı da, bunların uzayda birbirine ne kadar yakın olduğudur. Burada kullanılan ve bizim de kullanacağımız en popüler uzaklık metriği ise “cosine distance”. Daha büyük bir cosine distance, vektörlerin önemli ölçüde farklı yönlere işaret ettiğini gösterir; bu da temsil ettikleri özelliklerin çok farklı olduğu anlamına gelir.

Benzer bir yaklaşım ChatGPT gibi sohbet robotlarının kelimeleri, cümleleri ifade ediş yönteminde de bulunuyor. Bunu çok güzel bir şekilde ifade eden 3Blue1Brown kanalının videosunun bir kısmını şuraya bırakıyorum:

[embed]

Ne bu transfer learning?

Günümüzde yapay zeka problemlerini çözmek için yüzlerce katmanlı derin öğrenme mimarileri kullanılıyor. Ve her bir katman modelin öğrenmesi gereken ekstra milyonlarca parametre anlamına geliyor. Kısıtlı donanım ve veri seti imkanıyla da bu devasa mimarileri efektif bir şekilde eğitmek günler, haftalar, hatta aylar bile sürebiliyor. Tam bu noktada ise devreye Transfer Learning giriyor.

Transfer Learning, önceden eğitilmiş bir modelin başlangıç noktasını referans alarak yeni bir amaç için tekrardan eğitilmesi tekniğidir. Temel olarak, ilk modelin eğitilme amacıyla benzer bir sorunu çözmek için, modelin edindiği bilgilerden yararlanılmasıdır.

Daha iyi anlamak için classification (sınıflandırma) modelleri üzerinden şöyle hayal edebiliriz: Aslında classification modellerinin katmanlarının büyük bir kısmı asıl amaç olan classification problemini çözmekten ziyade önce görselin ne olduğunu anlamaya çalışıyor. Yüzlerce katman boyunca her katmanda o görselin hangi sınıfa ait olduğunu çözmekten ziyade, görsel hakkında çok kapsamlı bir bilgiye sahip olmakla ilgileniyor. Katmanlar ilerledikçe görsel üzerindeki anlayışı da yavaş yavaş gelişiyor. Son katmanlarda ise geriye sadece çok iyi anladığı görselin hangi ayrık sınıfa ait olduğunun bağlantısını çözmek kalıyor. Bu, bütün fotoğraflar üzerinden yüksek bir anlayış oluşturmaktan çok daha basit bir problem olduğu için, görseller üzerinde yüksek anlayışa sahip modeli, bu anlayışı kendi veri uzayımıza ve sınıflarımıza dönüştürmek çok daha basit oluyor.

Öneri motorunu geliştirirken takip edeceğimiz başlıklar:

  1. DeepFashion2 Veri Seti
  2. Pre-trained ResNet50 ve **Transfer Learning**
  3. **Öneri Motorunu Geliştirmek**
  4. Sonuç

DeepFashion2 Veri Seti

DeepFashion2

DeepFashion2

İlk olarak bize öneri motorunu üzerinde eğitebileceğimiz bir veri seti lazım. Problemimiz için örnek olması açısından DeepFashion2 veri setini kullanacağız. DeepFashion2 hem ticari alışveriş mağazalarından hem de tüketicilerden 13 giyim kategorisinin 491 bin farklı görselini içerir. Ben ise Transfer Learning’in etkisini gösterebilmek adına sadece 34000 fotoğraf içeren “validation” diye ayırdıkları veri setini kullanacağım.

Takip edeceğim adımlar, herhangi çeşitteki ürünlere adapte edilebilir. Daha anlaşılır ve tekrarlanabilir olması için hazır bir veri seti kullandım ama kendi kullanımınıza uygun problemler için de basit bir “web-scrapping” yöntemiyle veri setinizi oluşturmanız pekala mümkün.

Veriyi düzenlemek

Validation veri setini indirdikten sonra karşımıza şöyle bir klasör yapısı çıkacak (klasör adını “validation”dan “DeepFashion”a çevirdim):

├──DeepFashion
  ├── annos
  │     ├── 000001.json
  │     ├── 000002.json
  │     │      ...
  │     └── 032153.json
  └── image
        ├── 000001.jpg
        ├── 000002.jpg
        │      ...
        └── 032153.jpg

Her bir fotoğraf için bounding box anotasyonları ve sınıf isimleri .json dosyalarında mevcut. Fotoğrafları bir bütün olarak değil, anotasyonlara göre kesilmiş hallerini kullanabilmek için bunları DeepFashion/crops klasörüne kaydeden ve modeli eğitmekte kullanacağımız pandas.DataFrame’i oluşturmak için:

import os
import pandas as pd

def get_crop(image, bounding_box):
    image_height, image_width = image.shape[0], image.shape[1]
    x, y, x2, y2 = bounding_box
    width=x2-x
    height=y2-y

    # HANDLE NEGATIVE HEIGHT AND WIDTH
    min_h, max_h = min(int(y), int(y+height)), max(int(y), int(y+height))
    min_w, max_w = min(int(x), int(x+width)), max(int(x), int(x+width))
    # HANDLE EXCEEDING COORDINATES
    min_h, max_h = max(0, min_h), min(max_h, image_height)
    min_w, max_w = max(0, min_w), min(max_w, image_width)

    return image[min_h:max_h, min_w:max_w]

dataset_name = 'DeepFashion'

os.makedirs(os.path.join(dataset_name, 'crops'), exist_ok=True)

number_of_data = len(os.listdir(os.path.join(dataset_name, 'annos')))
print(f'number of annotations: {number_of_data}')

# Initialize an empty list to store image file paths and their respective labels
data = []

# Initialize crop index of which we'll save image crops
crop_index = 1

# Iterate over each trash type (folder) to process images
for i in range(1, number_of_data+1):
    id = f'{i:06d}'

    with open(f'./{dataset_name}/annos/{id}.json', mode='r') as f:
        annotation = json.load(f)

    number_of_items = 0
    for ann_key in annotation:
        if 'item' in ann_key: number_of_items+=1

    image = cv2.imread(f'./{dataset_name}/image/{id}.jpg')

    for j in range(1, number_of_items+1):
        cropped_image = get_crop(image, annotation[f'item{j}']['bounding_box'])
        crop_path = os.path.join(dataset_name, 'crops', f'{crop_index:06d}.jpg')
        crop_index += 1
        cv2.imwrite(crop_path, cropped_image)

        data.append((crop_path, annotation[f'item{j}']['category_name']))

# Convert the collected data into a DataFrame
df = pd.DataFrame(data, columns=['filepath', 'label'])

# Display the first few entries of the DataFrame
df.head(), f'number of items: {len(df)}'

Veriyi bölmek:

Oluşturduğumuza DataFrame’i train ve validation olarak ikiye bölüyoruz.

from sklearn.model_selection import train_test_split

# Split with stratification
train_df, val_df = train_test_split(df, test_size=0.2, random_state=42, stratify=df['label'])

# Print the number of images in each set
print(f"Number of images in the training set: {len(train_df)}")
print(f"Number of images in the validation set: {len(val_df)}")

num_classes = len(train_df['label'].unique())
print(f'Number of classes: {num_classes}')

val_df

Pre-trained ResNet50 ve Transfer Learning

ResNet50 model mimarisi

ResNet50 model mimarisi

Bu noktada bize bir derin öğrenme mimarisi ve bu mimarinin bizim problemimize uygun bir veri setinde eğitilmiş olan ağırlıkları lazım. Günümüzde transformer temelli derin öğrenme modelleri son derece revaçta. Özellikle en son çıkan Sora, DALL-E gibi modeller de bu transformer furyasının en çarpıcı örneklerinden sadece ikisi. Ancak ben, hem kullanım kolaylığı açısından hem de çözmeye çalıştığımız problemin aslında ne kadar basit olduğunu göstermek açısından görece eski bir mimari olan ResNet50’yi kullanacağım.

Kullanacağımız kütüphaneler, fonksiyonlar ve parametreler:

from keras.applications import ResNet50
from keras.applications.resnet50 import preprocess_input
from keras.layers import Dense, GlobalAveragePooling2D, Dropout
from keras.models import Model
from keras.optimizers import Adam
from keras.losses import CategoricalCrossentropy
from keras.preprocessing.image import ImageDataGenerator
from keras.callbacks import ReduceLROnPlateau, EarlyStopping, ModelCheckpoint

target_size = (224, 224)
lr = .00001
batch_size = 64

Modeli tanımlamak:

Modelimizi 1000 sınıflık Imagenet veri seti üzerinde eğitilmiş bir kaynak model ile eğiteceğiz. Bizim veri setimizde 13 tane sınıf olduğu için modelin son katmanlarını buna uygun şekilde değiştirmemiz gerekiyor.

# Load the ResNet50 model with weights pre-trained on ImageNet
base_model = ResNet50(weights='imagenet', include_top=False, input_shape=target_size+(3,))

for layer in base_model.layers[:143]:
    layer.trainable = False

# Create the new model
x = base_model.output
x = GlobalAveragePooling2D()(x)
x = Dropout(0.5)(x)
x = Dense(num_classes, activation='softmax')(x)

transfer_resnet50_model = Model(inputs=base_model.input, outputs=x)

# Compile the model
transfer_resnet50_model.compile(optimizer=Adam(learning_rate=lr), loss=CategoricalCrossentropy(), metrics=['accuracy'])

Burada vurgulamak istediğim birkaç nokta var. Birincisi, önceden eğitilmiş modeli çağırırken include_top parametresini False yapmak. Bu bize görsel özelliklerin olduğu vektörün hangi sınıfa ait olduğunu hesaplayan son katmanı modele dahil etmememizi sağlıyor. İkincisi, modelin ilk 143 katmanını dondurmamız, yani eğitim aşamasında o katmandaki parametreleri değiştirmeyecek olmamız. Son olarak da modelin sonuna yeni birkaç katman eklememiz. Bunların hepsinin ortak amacı; önceden eğitilmiş modelin, görsel üzerindeki oluşturduğu anlayışı kullanırken, aynı zamanda kendi veri setimize ve sınıflarımıza adapte olmasını sağlamak.

ImageDataGenerator ile pre-processing ve data augmentation adımları:

Tekstil ürünleri genellikle benzer görünümlerden, açılardan, konumlardan vb. çekilme eğilimindedir. Modelimizin overfit olmasını önlemek ve veri üzerinde daha iyi bir anlayış geliştirmek için görselleri eğitim aşamasından önce rastgele döndürecek, kaydıracak, yakınlaştıracak, çevirecek “data augmentation” adımı uygulayacağız.

# Slight Augmentation settings for training
train_datagen = ImageDataGenerator(
    rotation_range=60,                  # Randomly rotate the images by up to 60 degrees
    width_shift_range=0.15,             # Randomly shift images horizontally by up to 15% of the width
    height_shift_range=0.15,            # Randomly shift images vertically by up to 15% of the height
    zoom_range=0.20,                    # Randomly zoom in or out by up to 20%
    horizontal_flip=True,               # Randomly flip images horizontally
    vertical_flip=True,                 # Randomly flip images vertically
    shear_range=0.05,                   # Apply slight shear transformations
    brightness_range=[0.9, 1.1],        # Vary brightness between 90% to 110% of original
    channel_shift_range=10,             # Randomly shift channels (can change colors of images slightly but less aggressively)
    fill_mode='nearest',                 # Fill in missing pixels using the nearest filled value
    preprocessing_function=preprocess_input  # Add this line
)

# For the validation set, you might not have augmentation:
val_datagen = ImageDataGenerator(preprocessing_function=preprocess_input)  # Add this line

# Using flow_from_dataframe to generate batches
train_generator = train_datagen.flow_from_dataframe(
    dataframe=train_df,                  # DataFrame containing training data
    x_col="filepath",                    # Column with paths to image files
    y_col="label",                       # Column with image labels
    target_size=target_size,              # Resize all images to size of 384x384
    batch_size=batch_size,                       # Number of images per batch
    class_mode='categorical',            # One-hot encode labels
    seed=42,                             # Seed for random number generator to ensure reproducibility
    shuffle=False                        # Data is not shuffled; order retained from DataFrame
)

# Generate validation batches from the validation dataframe
val_generator = val_datagen.flow_from_dataframe(
    dataframe=val_df,                    # DataFrame containing validation data
    x_col="filepath",                    # Column with paths to image files
    y_col="label",                       # Column with image labels
    target_size=target_size,              # Resize all images to size of 384x384
    batch_size=batch_size,                       # Number of images per batch
    class_mode='categorical',            # One-hot encode labels
    seed=42,                             # Seed for random number generator to ensure reproducibility
    shuffle=False                        # Data is not shuffled; order retained from DataFrame
)

(Kodun bu kısmı şuradan alınmıştır: Imbalanced Garbage Classification | ResNet50)

Modeli eğitmek (büyünün gerçekleştiği yer):

# Define the callbacks
reduce_lr = ReduceLROnPlateau(monitor='val_loss', factor=0.5, patience=5, min_lr=0.00001)
early_stopping = EarlyStopping(monitor='val_loss', mode='min', patience=15, restore_best_weights=True, verbose=1)
# Define a callback to save the model weights after each epoch
checkpoint_callback = ModelCheckpoint(
    filepath='weights/epoch_{epoch:02d}.h5',  # Save the weights with epoch number in the filename
    save_weights_only=True,  # Only save the weights
    save_freq='epoch'  # Save after each epoch
)

# Total number of epochs
num_epochs = 50

# Train the model
history = transfer_resnet50_model.fit(train_generator,
                                      epochs=num_epochs,
                                      validation_data=val_generator, 
                                      validation_steps=len(val_generator),
                                      callbacks=[reduce_lr, early_stopping, checkpoint_callback])

Birkaç saatlik CPU üzerindeki eğitim sonucunda yalnızca 46 epochta %94 train ve %85'lere varan bir performans elde edebildim:

Öneri Motorunu Geliştirmek

Artık elimizdeki veri seti üzerinde yüksek anlayışa sahip bir ResNet50 modeli olduğuna göre, öneri motorunun kendisini geliştirmeye başlayabiliriz. Bu başlıkta veri setindeki rastgele seçilen bir ürüne en çok benzeyen 10 ürünü bulan örnek bir program geliştireceğiz.

Ürünlerin görsel özellik vektörlerini kaydetmek:

Yukarıda anlattığım gibi görsellerin benzerliklerini karşılaştırabilmek için cosine distance hesabını kullanacağız. Bunun için de eğittiğimiz modelin çıktı olarak bize bir vektör vermesi lazım ancak şu anki haliyle çıktı olarak 13 sınıftan birini verecektir. Bunun için modelin son 2 katmanını çıkarıyoruz:

# Remove classification layers
similarity_model = Model(inputs=transfer_resnet50_model.input, outputs=transfer_resnet50_model.layers[-2].output)

Amacımız girdi olarak verilen ürünün, veri setinde en çok hangi ürünlere benzediğini bulmak. Ancak her yeni bir ürün görseline en çok benzeyen ürünleri bulmaya çalışırken her seferinde bütün fotoğrafları tek tek modelden geçirmek onlarca dakika süren çok yavaş bir işlem olacaktır. Ürün görselleri arasında hızlıca karşılaştırma yapabilmek için öncelikle elimizdeki bütün görselleri bir kere modelden geçirip görsel özellik vektörlerini oluşturmamız lazım:

def compute_embeddings(embedding_model: Model, dataset_path: str = 'DeepFashion'):

    print('DATASET EMBEDDINGS ARE BEING COMPUTED..')
    all_images = list()
    crops_path = os.path.join(dataset_path, 'crops')
    for image_file in os.listdir(crops_path):
        image_path = os.path.join(crops_path, image_file)
        all_images.append((image_path, ''))

    dataset_df = pd.DataFrame(all_images, columns=['filepath', 'label'])

    # Generate validation batches from the validation dataframe
    dataset_generator = val_datagen.flow_from_dataframe(
        dataframe=dataset_df,
        x_col="filepath",
        y_col="label",
        target_size=target_size,
        batch_size=batch_size,
        class_mode='categorical',
        seed=42,
        shuffle=False
    )

    all_embeddings = embedding_model.predict(dataset_generator, steps=len(dataset_generator))

    return all_embeddings, all_image_paths

# Compute all embeddings
all_embeddings, all_image_paths = compute_embeddings(similarity_model)

En çok benzeyen vektörün hangi görsele ait olduğunu bulmak için aynı zamanda görsellerin isimlerini de kaydediyoruz.

Cosine distance fonksiyonu:

Ürün “benzerliklerini” hesaplayabilmek için:

import numpy as np

def cosine_dist(element: np.ndarray, arr: np.ndarray):
    anorm = np.linalg.norm(arr, axis=1)
    bnorm = np.linalg.norm(element, axis=1)
    return ((1.0 - np.dot(arr, element.T) / np.outer(anorm, bnorm)) / 2.0)

Örnek çıktılar için script:

Veri setinden rastgele 10 fotoğraf alan ve bunlara en çok benzeyen 10 tane fotoğrafı matplotlib kütüphanesi ile gösteren script:

import matplotlib.pyplot as plt

def get_n_random_files(folder_path="./DeepFashion/crops/", n = 10):
    all_files = list()
    all_files += [os.path.join(folder_path, f) for f in os.listdir(folder_path) if f.endswith(('jpg', 'jpeg', 'png'))]
    random_files = random.sample(all_files, n)
    return random_files

random_input_image_paths = get_n_random_files()

print(f'input_image_paths: {random_input_image_paths}')
input_images = [cv2.imread(i)[:,:,::-1] for i in random_input_image_paths]
data_df = pd.DataFrame(random_input_image_paths, columns=['filepath'])
input_generator = val_datagen.flow_from_dataframe(
    dataframe=data_df,
    x_col="filepath",
    target_size=target_size,
    batch_size=batch_size,
    class_mode=None,
    seed=42,
    shuffle=False
)

# PREDICT INPUTS
img_embedding = similarity_model.predict(input_generator, steps=len(input_generator))

# CALCULATE DISTANCES
print('CALCULATING MATCHING PRODUCTS')
distances = cosine_dist(img_embedding, all_embeddings)

similar_img_lists = list()
for ind in range(distances.shape[-1]):
    smallest_indices = np.argsort(distances, axis=0)[:12, ind]

    # Example usage
    image_paths = [all_image_paths[i] for i in smallest_indices]
    images = [cv2.imread(i)[:,:,::-1] for i in image_paths]

    similar_img_lists.append(images)

# Show outputs with matplotlib 
for i in range(len(similar_img_lists)):
    fig, axes = plt.subplots(nrows=2, ncols=5, figsize=(20, 8))

    for j in range(2): 
        for k in range(5):
            axes[j][k].imshow(similar_img_lists[i][(j+1)*(k+1)-1])
    plt.show()

Sonuç

İlk fotoğrafın girdi, diğer fotoğrafların çıktı oluğu bazı örnekler:

Yaptığımız adımların her birinde geliştirilebilecek, optimize edilebilecek çok fazla nokta mevcut. Ancak elimden geldiğince basit bir anlatım ve metot kullanmaya çalıştım. Bu şekilde bile hızlıca gayet makul sonuçlar etmeyi başardık ve anlattıklarımı, kendi problemleriniz için adapte edip geliştirebilirsiniz.

Bi’ not Yapay zeka alanındaki teknikleri, makine öğrenmesi modellerinin girdi olarak aldığı verilere nasıl “anlam” yüklediğini ve bunların günlük pratiklerdeki kullanım karşılıklarını anlamanın önemli olduğuna inanıyorum. Bu alanda dışarıdan bize “wow!” gelen ve her gün artan yeniliklerin temel mantığını öğrenmek bize bir bakış açısı, vizyon ve yeni yaklaşımlar katacaktır.

Herhangi bir soru veya paylaşımınız için **LinkedIn** üzerinden ulaşabilirsiniz.


메타데이터
post_id
12611de663fd
slug
transfer-learning-metoduyla-öneri-motoru-geliştirmek-12611de663fd
url
https://medium.com/@koseorhun13/transfer-learning-metoduyla-%C3%B6neri-motoru-geli%C5%9Ftirmek-12611de663fd
canonical_url
https://medium.com/@koseorhun13/transfer-learning-metoduyla-%C3%B6neri-motoru-geli%C5%9Ftirmek-12611de663fd
author_url
https://medium.com/@koseorhun13
status
ok
fetched_at
2026-07-11 23:37:18