Skip to content

Instantly share code, notes, and snippets.

@seyyah
Last active May 12, 2025 21:39
Show Gist options
  • Select an option

  • Save seyyah/1b46ef51a5ad5385f3ce9a17432052bc to your computer and use it in GitHub Desktop.

Select an option

Save seyyah/1b46ef51a5ad5385f3ce9a17432052bc to your computer and use it in GitHub Desktop.
import math
import numpy as np
from typing import Dict, List, Tuple, Any, Optional
class BunderMatchingEngine:
"""
Bunder - B2B Firma Eşleştirme için Çok Boyutlu Benzerlik Hesaplama Motoru
İşletmeler arası eşleştirme için background, goals, interests ve location
vektörleri üzerinden çok boyutlu benzerlik ve tamamlayıcılık hesaplayan algoritma.
"""
def __init__(self, dimensions: List[str], weights: Optional[Dict[str, float]] = None):
"""
Parameters:
-----------
dimensions: List[str]
Hesaplama yapılacak boyutların listesi
(örn: ['background', 'goals', 'interests', 'location'])
weights: Dict[str, float], optional
Her boyut için ağırlıklar (toplamı 1 olmalı)
"""
self.dimensions = dimensions
# Varsayılan ağırlıklar (gerekirse özelleştirilebilir)
self.weights = weights or {
'background': 0.25,
'goals': 0.40,
'interests': 0.25,
'location': 0.10
}
# Eksik boyutlar için uyarı
missing_weights = [dim for dim in dimensions if dim not in self.weights]
if missing_weights:
raise ValueError(f"Missing weights for dimensions: {missing_weights}")
# Ağırlıkların toplamının 1'e yakın olmasını kontrol et
weight_sum = sum(self.weights.values())
if not (0.99 <= weight_sum <= 1.01):
raise ValueError(f"Sum of weights should be 1.0, got {weight_sum}")
# Çeşitli benzerlik ölçütleri için yöntem haritası
self.similarity_methods = {
'background': self._cosine_similarity, # Geçmiş için benzerlik
'goals': self._complementarity_score, # Hedefler için tamamlayıcılık
'interests': self._weighted_jaccard, # Meraklar için ağırlıklı kesişim
'location': self._geographic_proximity # Lokasyon için yakınlık
}
# Geçmiş başarılı eşleşmeler için hafıza
self.successful_matches = []
self.match_history = []
def _cosine_similarity(self, vec1: List[float], vec2: List[float]) -> float:
"""
Kosinüs benzerliği: İki vektör arasındaki açının kosinüsü
Parameters:
-----------
vec1, vec2: List[float]
Karşılaştırılacak vektörler
Returns:
--------
float:
0 (hiç benzer değil) ile 1 (tamamen aynı) arasında benzerlik skoru
"""
if len(vec1) != len(vec2):
raise ValueError(f"Vectors must have the same length, got {len(vec1)} and {len(vec2)}")
dot_product = sum(a*b for a, b in zip(vec1, vec2))
magnitude1 = math.sqrt(sum(a*a for a in vec1))
magnitude2 = math.sqrt(sum(b*b for b in vec2))
if magnitude1 * magnitude2 == 0:
return 0
return dot_product / (magnitude1 * magnitude2)
def _complementarity_score(self, vec1: List[float], vec2: List[float]) -> float:
"""
Tamamlayıcılık skoru:
Bazı boyutlarda benzerlik değil tamamlayıcılık önemlidir
Parameters:
-----------
vec1, vec2: List[float]
Karşılaştırılacak vektörler (0-1 aralığında normalize edilmiş)
Returns:
--------
float:
0 (hiç tamamlayıcı değil) ile 1 (tamamen tamamlayıcı) arasında skor
"""
if len(vec1) != len(vec2):
raise ValueError(f"Vectors must have the same length, got {len(vec1)} and {len(vec2)}")
# Örnek: vec1'in yüksek olduğu yerde vec2 düşükse ve tersi geçerliyse
# bunlar tamamlayıcı olabilir
total_score = 0
for i in range(len(vec1)):
# Biri yüksek diğeri düşükse tamamlayıcılık yüksek
score = (vec1[i] * (1 - vec2[i]) + vec2[i] * (1 - vec1[i])) / 2
total_score += score
return total_score / len(vec1)
def _weighted_jaccard(self, vec1: List[float], vec2: List[float]) -> float:
"""
Ağırlıklı Jaccard indeksi: Kategorik değerler için set benzerliği
Parameters:
-----------
vec1, vec2: List[float]
Binary vektörler (0,1) - 1: İlgili kategori/özellik mevcut, 0: Mevcut değil
Returns:
--------
float:
0 (hiç örtüşme yok) ile 1 (tamamen örtüşüyor) arasında benzerlik skoru
"""
if len(vec1) != len(vec2):
raise ValueError(f"Vectors must have the same length, got {len(vec1)} and {len(vec2)}")
# 0-1 aralığında olup olmadığını kontrol et
if not all(0 <= x <= 1 for x in vec1 + vec2):
raise ValueError("All values in vectors must be between 0 and 1")
intersect_sum = sum(min(a, b) for a, b in zip(vec1, vec2))
union_sum = sum(max(a, b) for a, b in zip(vec1, vec2))
if union_sum == 0:
return 0
return intersect_sum / union_sum
def _geographic_proximity(self, loc1: Tuple[float, float], loc2: Tuple[float, float], max_distance: float = 500) -> float:
"""
Coğrafi yakınlık skoru
Parameters:
-----------
loc1, loc2: Tuple[float, float]
(lat, long) formatında konum vektörleri
max_distance: float
km cinsinden maksimum anlamlı mesafe
Returns:
--------
float:
0 (çok uzak) ile 1 (çok yakın) arasında yakınlık skoru
"""
# Haversine formülü ile kuş uçuşu mesafeyi hesapla
distance = self._haversine_distance(loc1, loc2)
# Mesafeyi 0-1 aralığında normalize et (uzaklık arttıkça skor düşer)
proximity = max(0, 1 - (distance / max_distance))
return proximity
def _haversine_distance(self, loc1: Tuple[float, float], loc2: Tuple[float, float]) -> float:
"""
İki konum arasındaki dünya üzerindeki mesafeyi hesapla (km)
Parameters:
-----------
loc1, loc2: Tuple[float, float]
(latitude, longitude) formatında konum vektörleri
Returns:
--------
float:
İki konum arasındaki mesafe (km)
"""
lat1, lon1 = loc1
lat2, lon2 = loc2
# Dünya yarıçapı (km)
R = 6371
# Radyan cinsinden hesaplamalar
dLat = math.radians(lat2 - lat1)
dLon = math.radians(lon2 - lon1)
lat1 = math.radians(lat1)
lat2 = math.radians(lat2)
a = math.sin(dLat/2)**2 + math.cos(lat1) * math.cos(lat2) * math.sin(dLon/2)**2
c = 2 * math.atan2(math.sqrt(a), math.sqrt(1-a))
distance = R * c
return distance
def calculate_similarity(self, company1: Dict[str, Any], company2: Dict[str, Any]) -> Dict[str, Any]:
"""
İki firma arasındaki çok boyutlu benzerlik/uyum skorunu hesapla
Parameters:
-----------
company1, company2: Dict[str, Any]
Her boyut için vektör içeren firma profilleri
Returns:
--------
Dict[str, Any]:
Toplam skor, boyut skorları ve eşleşme tipi içeren sonuç dictionary
"""
# Tüm boyutların mevcut olduğunu kontrol et
if not all(dim in company1 and dim in company2 for dim in self.dimensions):
missing1 = [dim for dim in self.dimensions if dim not in company1]
missing2 = [dim for dim in self.dimensions if dim not in company2]
raise ValueError(f"Missing dimensions: {missing1} for company1, {missing2} for company2")
# Her boyut için benzerlik skorunu hesapla
dimension_scores = {}
for dimension in self.dimensions:
# Doğru benzerlik metodu seçimi
similarity_method = self.similarity_methods.get(dimension, self._cosine_similarity)
# Benzerlik hesaplama
vec1 = company1[dimension]
vec2 = company2[dimension]
score = similarity_method(vec1, vec2)
dimension_scores[dimension] = score
# Ağırlıklı toplam skor hesaplama
total_score = sum(self.weights[dim] * dimension_scores[dim] for dim in self.dimensions)
# Eşleştirme tipi belirleme
match_type = self._determine_match_type(dimension_scores)
# Sonuç objesi
result = {
'total_score': total_score,
'dimension_scores': dimension_scores,
'match_type': match_type,
'company1_id': company1.get('id', 'unknown'),
'company2_id': company2.get('id', 'unknown')
}
# Hafızaya ekle
self.match_history.append(result)
return result
def _determine_match_type(self, dimension_scores: Dict[str, float]) -> str:
"""
Boyut skorlarına bakarak eşleştirme tipini belirle
Parameters:
-----------
dimension_scores: Dict[str, float]
Her boyut için hesaplanmış benzerlik skorları
Returns:
--------
str:
Eşleştirme tipi (STRATEGIC_PARTNER, INDUSTRY_ALLY, vs.)
"""
# Öncelikli olarak tamamlayıcılık ve benzerlik kombinasyonuna bak
goals_score = dimension_scores.get('goals', 0)
background_score = dimension_scores.get('background', 0)
interests_score = dimension_scores.get('interests', 0)
location_score = dimension_scores.get('location', 0)
if goals_score > 0.8 and background_score > 0.6:
return "STRATEGIC_PARTNER"
elif background_score > 0.7 and goals_score > 0.6:
return "INDUSTRY_ALLY"
elif interests_score > 0.8 and goals_score > 0.5:
return "INNOVATION_PARTNER"
elif location_score > 0.9 and (background_score > 0.4 or goals_score > 0.4):
return "LOCAL_PARTNER"
elif goals_score > 0.7:
return "GOAL_ALIGNED_PARTNER"
elif background_score > 0.7:
return "BACKGROUND_ALIGNED_PARTNER"
else:
return "GENERAL_BUSINESS_PARTNER"
def find_top_matches(self, target_company: Dict[str, Any], all_companies: List[Dict[str, Any]],
top_n: int = 10, threshold: float = 0.6) -> List[Dict[str, Any]]:
"""
Hedef firma için en uygun eşleşmeleri bul
Parameters:
-----------
target_company: Dict[str, Any]
Eşleşme aranacak hedef firma
all_companies: List[Dict[str, Any]]
Karşılaştırılacak firmalar listesi
top_n: int
Döndürülecek en iyi eşleşme sayısı
threshold: float
Minimum eşleşme skoru (0-1 arası)
Returns:
--------
List[Dict[str, Any]]:
En yüksek skorlu eşleşmelerin listesi
"""
matches = []
for company in all_companies:
# Kendisiyle eşleştirme yapma
if company.get('id') == target_company.get('id'):
continue
try:
similarity = self.calculate_similarity(target_company, company)
if similarity['total_score'] >= threshold:
matches.append({
'company': company,
'similarity': similarity
})
except Exception as e:
print(f"Error calculating similarity for {company.get('id', 'unknown')}: {str(e)}")
continue
# En yüksek skorlara göre sırala
matches.sort(key=lambda x: x['similarity']['total_score'], reverse=True)
# En iyi n eşleşmeyi döndür
return matches[:top_n]
def update_weights_from_feedback(self, successful_matches: List[Dict[str, Any]], alpha: float = 0.1) -> None:
"""
Başarılı eşleşmelerden öğrenerek ağırlıkları güncelle
Parameters:
-----------
successful_matches: List[Dict[str, Any]]
Başarılı olarak işaretlenen eşleşmeler
alpha: float
Öğrenme oranı (0-1 arası)
"""
if not successful_matches:
return
# Başarılı eşleşmeleri hafızaya ekle
self.successful_matches.extend(successful_matches)
# Başarılı eşleşmelerdeki her boyut için ortalama skor hesapla
dimension_importance = {dim: 0 for dim in self.dimensions}
for match in successful_matches:
if 'similarity' not in match or 'dimension_scores' not in match['similarity']:
continue
for dimension in self.dimensions:
if dimension in match['similarity']['dimension_scores']:
dimension_importance[dimension] += match['similarity']['dimension_scores'][dimension]
# Ortalamaları al
for dimension in self.dimensions:
dimension_importance[dimension] /= len(successful_matches)
# Toplam önem hesapla
total_importance = sum(dimension_importance.values())
# Yeni ağırlıkları hesapla (yumuşak güncelleme)
if total_importance > 0:
for dimension in self.dimensions:
new_weight = dimension_importance[dimension] / total_importance
# Smooth update with learning rate
self.weights[dimension] = (1 - alpha) * self.weights[dimension] + alpha * new_weight
# Ağırlıkları normalize et (toplamları 1 olsun)
weight_sum = sum(self.weights.values())
if weight_sum > 0:
for dimension in self.dimensions:
self.weights[dimension] /= weight_sum
def get_analytics(self) -> Dict[str, Any]:
"""
Eşleştirme performansı ve analitik veri döndürür
Returns:
--------
Dict[str, Any]:
Eşleştirme istatistikleri ve ağırlık bilgileri
"""
# Performans istatistikleri
total_matches = len(self.match_history)
successful_matches = len(self.successful_matches)
# Ortalama skor
avg_score = 0
if total_matches > 0:
avg_score = sum(match['total_score'] for match in self.match_history) / total_matches
# Eşleştirme tipleri dağılımı
match_types = {}
for match in self.match_history:
match_type = match.get('match_type', 'UNKNOWN')
match_types[match_type] = match_types.get(match_type, 0) + 1
return {
'total_matches': total_matches,
'successful_matches': successful_matches,
'success_rate': successful_matches / total_matches if total_matches > 0 else 0,
'avg_score': avg_score,
'match_types': match_types,
'current_weights': self.weights
}
# Kullanım Örneği
if __name__ == "__main__":
# Örnek vektör verileri (gerçek verilerinize göre uyarlanmalı)
company1 = {
'id': 'company1',
'name': 'ABC Tech',
'background': [0.8, 0.2, 0.5, 0.9], # Teknoloji, finans, e-ticaret, vb. alanlarındaki tecrübesi
'goals': [0.9, 0.7, 0.2, 0.1], # Büyüme, inovasyon, maliyet düşürme, vb. hedefleri
'interests': [1, 0, 1, 0, 1], # AI, blockchain, IoT, vb. ilgi alanları (binary)
'location': (41.0082, 28.9784) # İstanbul koordinatları
}
company2 = {
'id': 'company2',
'name': 'XYZ Software',
'background': [0.9, 0.1, 0.3, 0.8],
'goals': [0.3, 0.8, 0.9, 0.2],
'interests': [1, 0, 0, 1, 1],
'location': (39.9334, 32.8597) # Ankara koordinatları
}
# Matcher'ı oluştur
matcher = BunderMatchingEngine(
dimensions=['background', 'goals', 'interests', 'location']
)
# Tek bir eşleşme hesapla
similarity = matcher.calculate_similarity(company1, company2)
print(f"Eşleşme skoru: {similarity['total_score']:.4f}")
print(f"Boyut skorları: {similarity['dimension_scores']}")
print(f"Eşleşme tipi: {similarity['match_type']}")
# Tüm firmalar arasında eşleşme hesaplama örneği için kullanılabilir
# all_companies = [company1, company2, ...]
# top_matches = matcher.find_top_matches(company1, all_companies)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment