Last active
May 12, 2025 21:39
-
-
Save seyyah/1b46ef51a5ad5385f3ce9a17432052bc to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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