Machine Learning Implementation
Detailed explanation of the machine learning algorithms and implementation
Machine Learning Implementation
The Suggested Products feature leverages advanced machine learning techniques to provide intelligent product recommendations. This section explores the algorithms, implementation details, and optimization strategies used in the system.
Algorithm Overview
The system employs two complementary machine learning approaches:
- Apriori Algorithm: For discovering frequent item sets and association rules
- Recent Pattern Analysis: For real-time behavioral pattern recognition
Apriori Algorithm Implementation
What is the Apriori Algorithm?
The Apriori algorithm is a classic algorithm in machine learning used for frequent item set mining and association rule learning. It identifies frequent patterns in transactional data and generates association rules between different items.
graph TD
A[Transaction Data] --> B[Frequent 1-itemsets]
B --> C[Frequent 2-itemsets]
C --> D[Frequent 3-itemsets]
D --> E[Association Rules]
E --> F[Product Recommendations]
subgraph "Support Filtering"
B1[Count Item Frequency]
B2[Apply Minimum Support]
B1 --> B2
end
subgraph "Confidence Calculation"
C1[Generate Candidate Rules]
C2[Calculate Confidence]
C3[Apply Minimum Confidence]
C1 --> C2 --> C3
endKey Concepts
Support
The proportion of transactions that contain a particular item set.
Support(A) = Number of transactions containing A / Total number of transactionsExample: If 100 out of 1000 customers buy products A and B together, then:
Support(A,B) = 100/1000 = 0.1 (10%)Confidence
The likelihood that a transaction containing item A also contains item B.
Confidence(A → B) = Support(A,B) / Support(A)Example: If 80% of customers who buy product A also buy product B:
Confidence(A → B) = 0.8 (80%)Lift
Measures how much more likely item B is purchased when item A is purchased.
Lift(A → B) = Confidence(A → B) / Support(B)Implementation Details
PHP-ML Integration
The system uses the Phpml\Association\Apriori library for implementation:
use Phpml\Association\Apriori;
class GenerateSuggestedProductsService
{
private $support = 0.1; // Minimum support threshold
private $confidence = 0.5; // Minimum confidence threshold
public function apply_apriori($customer_orders)
{
// Initialize Apriori algorithm
$associator = new Apriori($this->support, $this->confidence);
// Prepare transaction data
$transactions = $this->prepareTransactions($customer_orders);
// Train the model
$associator->train($transactions['products'], $transactions['labels']);
// Generate frequent item sets
$frequentSets = $associator->apriori();
// Convert to association rules
$rules = $associator->getRules();
return $this->processResults($frequentSets, $rules);
}
private function prepareTransactions($orders)
{
$products = [];
$labels = [];
foreach ($orders as $order) {
$orderProducts = [];
foreach ($order['products'] as $product) {
$orderProducts[] = $product['id'];
}
$products[] = $orderProducts;
$labels[] = $order['customer_id'];
}
return ['products' => $products, 'labels' => $labels];
}
}Data Processing Pipeline
graph LR
A[Raw Orders] --> B[Data Validation]
B --> C[Customer Grouping]
C --> D[Transaction Formation]
D --> E[Support Calculation]
E --> F[Frequent Itemsets]
F --> G[Rule Generation]
G --> H[Confidence Filtering]
H --> I[Final Recommendations]Algorithm Workflow
Step 1: Data Collection and Preparation
public function getOrders($dates)
{
$query = DB::table($this->ordersTable)
->join($this->orderDetailsTable,
$this->ordersTable.'.id', '=',
$this->orderDetailsTable.'.'.$this->orderIdField)
->whereBetween($this->ordersTable.'.created_at', $dates)
->where($this->ordersTable.'.status', '!=', 'cancelled')
->select([
$this->ordersTable.'.'.$this->customerIdField.' as customer_id',
$this->orderDetailsTable.'.product_id',
$this->orderDetailsTable.'.quantity',
$this->ordersTable.'.created_at'
]);
return $query->get()->groupBy('customer_id');
}Step 2: Customer Segmentation
public function customers($orders)
{
$customers = collect();
foreach ($orders as $customerId => $customerOrders) {
// Filter customers with minimum order frequency
if (count($customerOrders) >= $this->minOrderCount) {
$customers->push([
'customer_id' => $customerId,
'orders' => $customerOrders,
'total_orders' => count($customerOrders),
'unique_products' => $customerOrders->pluck('product_id')->unique()->count()
]);
}
}
return $customers;
}Step 3: Transaction Processing
private function processCustomerOrders($customer)
{
$transactions = [];
// Group orders by date to form transactions
$ordersByDate = $customer['orders']->groupBy(function($order) {
return Carbon::parse($order->created_at)->format('Y-m-d');
});
foreach ($ordersByDate as $date => $dayOrders) {
$productIds = $dayOrders->pluck('product_id')->unique()->values()->toArray();
if (count($productIds) >= 2) { // Minimum 2 products for association
$transactions[] = $productIds;
}
}
return $transactions;
}Step 4: Apriori Algorithm Application
public function generateFrequentItemsets($transactions)
{
$apriori = new Apriori($this->support, $this->confidence);
// Generate frequent 1-itemsets
$frequentItemsets = [];
$itemCounts = [];
// Count individual items
foreach ($transactions as $transaction) {
foreach ($transaction as $item) {
$itemCounts[$item] = ($itemCounts[$item] ?? 0) + 1;
}
}
// Apply minimum support
$totalTransactions = count($transactions);
$minSupport = $this->support * $totalTransactions;
foreach ($itemCounts as $item => $count) {
if ($count >= $minSupport) {
$frequentItemsets[1][] = [$item];
}
}
// Generate higher-order itemsets
$k = 2;
while (!empty($frequentItemsets[$k - 1])) {
$candidates = $this->generateCandidates($frequentItemsets[$k - 1]);
$frequentItemsets[$k] = $this->pruneInfrequent($candidates, $transactions, $minSupport);
$k++;
}
return $frequentItemsets;
}Algorithm Parameters and Tuning
Support Threshold Configuration
// Configuration in config/eva.php
'suggested_products_apriori_support' => env('SUGGESTED_PRODUCTS_APRIORI_SUPPORT', 0.1),Impact of Support Values:
| Support | Effect | Use Case |
|---|---|---|
| 0.05 | More itemsets, potentially noisy | Large diverse catalogs |
| 0.1 | Balanced approach | General purpose |
| 0.2 | Fewer, more reliable itemsets | Specialized catalogs |
| 0.3+ | Very conservative, few results | High-confidence only |
Confidence Threshold Configuration
'suggested_products_confidence_support' => env('SUGGESTED_PRODUCTS_CONFIDENCE_SUPPORT', 0.5),Impact of Confidence Values:
| Confidence | Effect | Interpretation |
|---|---|---|
| 0.3 | Liberal recommendations | 30% likelihood |
| 0.5 | Moderate confidence | 50% likelihood |
| 0.7 | High confidence | 70% likelihood |
| 0.9+ | Very conservative | 90%+ likelihood |
Performance Optimizations
Memory Management
public function processInChunks($customers)
{
$chunkSize = config('eva.suggested_products_chunk_size', 30);
$customers->chunk($chunkSize)->each(function ($chunk) {
foreach ($chunk as $customer) {
try {
$this->processCustomer($customer);
} catch (OutOfMemoryError $e) {
Log::error("Memory limit exceeded for customer {$customer['id']}");
$this->logError($e->getMessage());
}
}
// Force garbage collection
gc_collect_cycles();
});
}Algorithm Efficiency Improvements
// Early termination for sparse data
private function shouldContinueProcessing($transactions)
{
$averageItemsPerTransaction = array_sum(array_map('count', $transactions)) / count($transactions);
$uniqueItems = count(array_unique(array_merge(...$transactions)));
// Don't process if data is too sparse
return $averageItemsPerTransaction >= 2 && $uniqueItems >= 5;
}
// Optimized candidate generation
private function generateCandidates($frequentItemsets)
{
$candidates = [];
$count = count($frequentItemsets);
for ($i = 0; $i < $count - 1; $i++) {
for ($j = $i + 1; $j < $count; $j++) {
$candidate = array_unique(array_merge($frequentItemsets[$i], $frequentItemsets[$j]));
// Prune if any subset is not frequent
if ($this->allSubsetsFrequent($candidate, $frequentItemsets)) {
$candidates[] = $candidate;
}
}
}
return $candidates;
}Recent Pattern Analysis
Algorithm Description
The recent pattern analysis complements the Apriori algorithm by focusing on immediate customer behavior rather than historical patterns.
graph TD
A[Customer Request] --> B[Fetch Recent Orders]
B --> C[Extract Order Patterns]
C --> D[Calculate Frequencies]
D --> E[Apply Business Rules]
E --> F[Generate Suggestions]
subgraph "Pattern Recognition"
C1[Order Cycles]
C2[Product Frequencies]
C3[Quantity Patterns]
C4[Timing Analysis]
end
subgraph "Business Logic"
E1[Price Lists]
E2[Stock Availability]
E3[Customer Preferences]
E4[Seasonal Adjustments]
endImplementation
class GetSuggestedProductsFromRecentOrdersService
{
public function getSuggestions($customerId, $options = [])
{
$recentOrders = $this->getRecentOrders($customerId, $options);
$patterns = $this->analyzePatterns($recentOrders);
$suggestions = $this->generateSuggestions($patterns, $customerId);
return $this->enrichWithBusinessData($suggestions, $customerId);
}
private function analyzePatterns($orders)
{
$patterns = [];
foreach ($orders as $order) {
foreach ($order->details as $detail) {
$productId = $detail->product_id;
if (!isset($patterns[$productId])) {
$patterns[$productId] = [
'frequencies' => [],
'quantities' => [],
'intervals' => []
];
}
$patterns[$productId]['frequencies'][] = 1;
$patterns[$productId]['quantities'][] = $detail->quantity;
$patterns[$productId]['intervals'][] = $order->created_at;
}
}
return $this->calculateStatistics($patterns);
}
private function calculateStatistics($patterns)
{
foreach ($patterns as $productId => &$pattern) {
$pattern['frequency_score'] = count($pattern['frequencies']);
$pattern['avg_quantity'] = array_sum($pattern['quantities']) / count($pattern['quantities']);
$pattern['consistency_score'] = $this->calculateConsistency($pattern['intervals']);
$pattern['trend_score'] = $this->calculateTrend($pattern['quantities']);
}
return $patterns;
}
}Machine Learning Metrics
Evaluation Metrics
class ModelEvaluator
{
public function calculateAccuracy($predictions, $actualOrders)
{
$correct = 0;
$total = count($predictions);
foreach ($predictions as $prediction) {
if ($this->isProductOrdered($prediction['product_id'], $actualOrders)) {
$correct++;
}
}
return $total > 0 ? $correct / $total : 0;
}
public function calculatePrecision($predictions, $actualOrders)
{
$truePositives = 0;
$falsePositives = 0;
foreach ($predictions as $prediction) {
if ($this->isProductOrdered($prediction['product_id'], $actualOrders)) {
$truePositives++;
} else {
$falsePositives++;
}
}
return ($truePositives + $falsePositives) > 0 ?
$truePositives / ($truePositives + $falsePositives) : 0;
}
public function calculateRecall($predictions, $actualOrders)
{
$predictedProducts = collect($predictions)->pluck('product_id');
$actualProducts = collect($actualOrders)->pluck('product_id');
$truePositives = $predictedProducts->intersect($actualProducts)->count();
$falseNegatives = $actualProducts->diff($predictedProducts)->count();
return ($truePositives + $falseNegatives) > 0 ?
$truePositives / ($truePositives + $falseNegatives) : 0;
}
}Advanced Features
Seasonal Adjustment
private function applySeasonalAdjustment($suggestions)
{
$currentMonth = now()->month;
$seasonalFactors = config('eva.seasonal_factors', []);
foreach ($suggestions as &$suggestion) {
$productCategory = $suggestion['product_category'];
if (isset($seasonalFactors[$productCategory][$currentMonth])) {
$factor = $seasonalFactors[$productCategory][$currentMonth];
$suggestion['adjusted_quantity'] = $suggestion['quantity'] * $factor;
$suggestion['seasonal_factor'] = $factor;
}
}
return $suggestions;
}Customer Segmentation
private function getCustomerSegment($customerId)
{
$customer = Customer::find($customerId);
// RFM Analysis (Recency, Frequency, Monetary)
$recency = $this->calculateRecency($customer);
$frequency = $this->calculateFrequency($customer);
$monetary = $this->calculateMonetary($customer);
// Segment based on RFM scores
if ($recency >= 4 && $frequency >= 4 && $monetary >= 4) {
return 'champions';
} elseif ($recency >= 3 && $frequency >= 3) {
return 'loyal_customers';
} elseif ($recency >= 4) {
return 'new_customers';
} else {
return 'at_risk';
}
}Model Performance and Monitoring
Performance Tracking
class PerformanceTracker
{
public function trackRecommendationAccuracy()
{
$metrics = [
'total_recommendations' => 0,
'accepted_recommendations' => 0,
'conversion_rate' => 0,
'avg_order_value_increase' => 0
];
// Calculate metrics from actual vs predicted orders
$results = DB::table('order_recommendations')
->join('orders', 'order_recommendations.customer_id', '=', 'orders.customer_id')
->where('orders.created_at', '>=', now()->subDays(30))
->get();
foreach ($results as $result) {
$metrics['total_recommendations']++;
if ($this->wasRecommendationAccepted($result)) {
$metrics['accepted_recommendations']++;
}
}
$metrics['conversion_rate'] = $metrics['total_recommendations'] > 0 ?
$metrics['accepted_recommendations'] / $metrics['total_recommendations'] : 0;
return $metrics;
}
}A/B Testing Framework
class RecommendationABTest
{
public function assignTestGroup($customerId)
{
$hash = crc32($customerId . config('app.key'));
return ($hash % 100) < 50 ? 'control' : 'treatment';
}
public function getRecommendations($customerId, $context)
{
$group = $this->assignTestGroup($customerId);
if ($group === 'control') {
return $this->getBaselineRecommendations($customerId);
} else {
return $this->getMLRecommendations($customerId, $context);
}
}
public function trackConversion($customerId, $recommendedProducts, $actualOrder)
{
ABTestResult::create([
'customer_id' => $customerId,
'test_group' => $this->assignTestGroup($customerId),
'recommended_products' => json_encode($recommendedProducts),
'actual_order' => json_encode($actualOrder),
'conversion_rate' => $this->calculateConversion($recommendedProducts, $actualOrder)
]);
}
}This comprehensive machine learning implementation provides a robust foundation for intelligent product recommendations, combining proven algorithms with modern optimization techniques and monitoring capabilities.