mcpField
M
ml-supply-chain

ml-supply-chain

github.com/kishorkukrejaデータ・データベース8451 回インストール3 週間前に更新
リポジトリ

ml-supply-chain について

サプライチェーンチーム向けに、予測、分類、最適化、業務判断への機械学習の活用を案内します。課題設定、データ評価、モデル開発から、デプロイと監視計画まで扱います。

主な機能

  1. 01教師あり・教師なし学習の課題整理
  2. 02需要予測と遅延分類の指針
  3. 03クラスタリング、画像認識、NLPの解説
  4. 04モデルデプロイとMLOpsの概要

活用シーン

  1. 01サプライチェーンの需要予測モデル構築
  2. 02出荷遅延や品質不良の予測
  3. 03顧客セグメント化や業務異常の検出
  4. 04モデル導入と継続的な監視の計画

インストール

コマンドをコピーしてターミナルで実行します。

$npx skills add https://github.com/kishorkukreja/awesome-supply-chain --skill ml-supply-chain

インストール後、Claude Code で/ml-supply-chainを入力して読み込まれたか確認します。

詳細

サプライチェーンの機械学習

あなたは、サプライチェーンの課題に機械学習と人工知能を適用するエキスパートです。目標は、サプライチェーン業務全体の予測、分類、最適化、意思決定を改善する ML モデルの構築、トレーニング、デプロイを支援することです。

初期評価

サプライチェーンの課題に ML を適用する前に、以下を把握してください。

  1. ビジネス上の課題

    • 解決が必要な問題は何ですか?(需要予測、品質予測、ルート最適化)
    • ML モデルはどのような意思決定を支援しますか?
    • 現在のアプローチとその限界は何ですか?
    • 期待される改善と ROI は何ですか?
  2. データの利用可能性

    • どのようなデータを利用できますか?(構造化データ、非構造化データ、画像、テキスト)
    • 過去のデータ量はどれくらいですか?(ML には通常、1000 件以上のサンプルが必要です)
    • データ品質はどうですか?(欠損値、外れ値、ノイズ)
    • 特徴量は利用できますか?(予測変数)
    • リアルタイムでデータにアクセスできますか?
  3. ML の問題タイプ

    • 教師あり学習ですか?(ラベル付きデータを利用可能)
    • 教師なし学習ですか?(クラスタリング、異常検知)
    • 強化学習ですか?(逐次的な意思決定)
    • 時系列予測ですか?
  4. 技術環境

    • チーム内に ML の専門知識はありますか?
    • 計算リソースはありますか?(CPU、GPU、クラウド)
    • デプロイ環境はどのようなものですか?(バッチ、リアルタイム API、エッジ)
    • MLOps の能力はありますか?

サプライチェーンにおける ML の問題タイプ

教師あり学習

回帰(連続値の出力)

  • 需要予測
  • リードタイム予測
  • 価格最適化
  • 在庫レベル予測
  • 配送時間の推定

分類(カテゴリ値の出力)

  • 製品のカテゴリ分類
  • サプライヤーリスクの分類
  • 品質不良の検出
  • 出荷遅延の予測(定刻どおりか遅延か)
  • 顧客離反の予測
教師なし学習

クラスタリング

  • 顧客セグメンテーション
  • 製品のグループ化
  • ルートのクラスタリング
  • 業務における異常検知

次元削減

  • 特徴量の抽出
  • データの可視化
  • ノイズの低減
強化学習

逐次的な意思決定

  • 在庫管理ポリシー
  • 動的価格設定
  • 倉庫ロボットのナビゲーション
  • 注文の配車

ML による需要予測

需要予測のための特徴量エンジニアリング
import pandas as pd
import numpy as np
from datetime import datetime, timedelta

def create_demand_features(df, target_col='demand'):
    """
    Create comprehensive feature set for demand forecasting

    df: DataFrame with columns ['date', 'sku', 'demand']
    """

    df = df.copy()
    df['date'] = pd.to_datetime(df['date'])
    df = df.sort_values(['sku', 'date'])

    # Time-based features
    df['year'] = df['date'].dt.year
    df['month'] = df['date'].dt.month
    df['day'] = df['date'].dt.day
    df['day_of_week'] = df['date'].dt.dayofweek
    df['day_of_year'] = df['date'].dt.dayofyear
    df['week_of_year'] = df['date'].dt.isocalendar().week
    df['quarter'] = df['date'].dt.quarter
    df['is_weekend'] = (df['day_of_week'] >= 5).astype(int)
    df['is_month_start'] = df['date'].dt.is_month_start.astype(int)
    df['is_month_end'] = df['date'].dt.is_month_end.astype(int)

    # Lag features (previous demand)
    for lag in [1, 7, 14, 28, 365]:
        df[f'lag_{lag}'] = df.groupby('sku')[target_col].shift(lag)

    # Rolling statistics
    for window in [7, 14, 28, 90]:
        df[f'rolling_mean_{window}'] = (
            df.groupby('sku')[target_col]
            .rolling(window, min_periods=1)
            .mean()
            .reset_index(0, drop=True)
        )

        df[f'rolling_std_{window}'] = (
            df.groupby('sku')[target_col]
            .rolling(window, min_periods=1)
            .std()
            .reset_index(0, drop=True)
        )

        df[f'rolling_min_{window}'] = (
            df.groupby('sku')[target_col]
            .rolling(window, min_periods=1)
            .min()
            .reset_index(0, drop=True)
        )

        df[f'rolling_max_{window}'] = (
            df.groupby('sku')[target_col]
            .rolling(window, min_periods=1)
            .max()
            .reset_index(0, drop=True)
        )

    # Exponential weighted moving average
    df['ewm_7'] = (
        df.groupby('sku')[target_col]
        .transform(lambda x: x.ewm(span=7).mean())
    )

    df['ewm_28'] = (
        df.groupby('sku')[target_col]
        .transform(lambda x: x.ewm(span=28).mean())
    )

    # Trend features
    df['demand_trend_7'] = df[f'lag_7'] - df[f'lag_14']
    df['demand_trend_28'] = df[f'lag_28'] - df[f'lag_56']

    # Cyclical encoding for seasonal features
    df['month_sin'] = np.sin(2 * np.pi * df['month'] / 12)
    df['month_cos'] = np.cos(2 * np.pi * df['month'] / 12)
    df['day_sin'] = np.sin(2 * np.pi * df['day_of_week'] / 7)
    df['day_cos'] = np.cos(2 * np.pi * df['day_of_week'] / 7)

    return df

# Example usage
demand_df = pd.DataFrame({
    'date': pd.date_range('2022-01-01', '2024-12-31', freq='D'),
    'sku': 'SKU_001',
    'demand': np.random.poisson(100, 1096)
})

features_df = create_demand_features(demand_df)
print(f"Features created: {len(features_df.columns)} columns")
需要予測のための勾配ブースティング
import xgboost as xgb
import lightgbm as lgb
from sklearn.model_selection import TimeSeriesSplit
from sklearn.metrics import mean_absolute_error, mean_squared_error
import matplotlib.pyplot as plt

class MLDemandForecaster:
    """
    Machine learning demand forecasting using gradient boosting
    """

    def __init__(self, model_type='xgboost'):
        self.model_type = model_type
        self.model = None
        self.feature_importance = None

    def prepare_data(self, df, target_col='demand', test_size=90):
        """
        Prepare train/test split for time series

        test_size: number of days to hold out for testing
        """

        # Remove rows with NaN (due to lag features)
        df_clean = df.dropna()

        # Identify feature columns (exclude date, sku, target)
        feature_cols = [col for col in df_clean.columns
                       if col not in ['date', 'sku', target_col]]

        # Time-based split
        split_date = df_clean['date'].max() - timedelta(days=test_size)

        train = df_clean[df_clean['date'] <= split_date]
        test = df_clean[df_clean['date'] > split_date]

        X_train = train[feature_cols]
        y_train = train[target_col]
        X_test = test[feature_cols]
        y_test = test[target_col]

        return X_train, X_test, y_train, y_test, feature_cols

    def train(self, X_train, y_train, params=None):
        """
        Train gradient boosting model
        """

        if self.model_type == 'xgboost':
            default_params = {
                'objective': 'reg:squarederror',
                'n_estimators': 1000,
                'learning_rate': 0.05,
                'max_depth': 6,
                'subsample': 0.8,
                'colsample_bytree': 0.8,
                'random_state': 42,
                'early_stopping_rounds': 50
            }

            if params:
                default_params.update(params)

            self.model = xgb.XGBRegressor(**default_params)

        elif self.model_type == 'lightgbm':
            default_params = {
                'objective': 'regression',
                'n_estimators': 1000,
                'learning_rate': 0.05,
                'num_leaves': 31,
                'feature_fraction': 0.8,
                'bagging_fraction': 0.8,
                'bagging_freq': 5,
                'random_state': 42
            }

            if params:
                default_params.update(params)

            self.model = lgb.LGBMRegressor(**default_params)

        # Train
        self.model.fit(
            X_train, y_train,
            eval_set=[(X_train, y_train)],
            verbose=100
        )

        # Store feature importance
        self.feature_importance = pd.DataFrame({
            'feature': X_train.columns,
            'importance': self.model.feature_importances_
        }).sort_values('importance', ascending=False)

    def predict(self, X):
        """Make predictions"""
        return self.model.predict(X)

    def evaluate(self, X_test, y_test):
        """
        Evaluate model performance
        """

        y_pred = self.predict(X_test)

        metrics = {
            'MAE': mean_absolute_error(y_test, y_pred),
            'RMSE': np.sqrt(mean_squared_error(y_test, y_pred)),
            'MAPE': np.mean(np.abs((y_test - y_pred) / y_test)) * 100,
            'Bias': np.mean(y_pred - y_test)
        }

        return metrics, y_pred

    def plot_results(self, y_test, y_pred, dates=None):
        """
        Visualize predictions vs actuals
        """

        fig, axes = plt.subplots(2, 1, figsize=(14, 8))

        # Time series plot
        if dates is not None:
            axes[0].plot(dates, y_test.values, label='Actual', linewidth=2)
            axes[0].plot(dates, y_pred, label='Predicted', linewidth=2, alpha=0.7)
        else:
            axes[0].plot(y_test.values, label='Actual', linewidth=2)
            axes[0].plot(y_pred, label='Predicted', linewidth=2, alpha=0.7)

        axes[0].set_title('Demand Forecast: Actual vs Predicted')
        axes[0].set_xlabel('Time')
        axes[0].set_ylabel('Demand')
        axes[0].legend()
        axes[0].grid(True, alpha=0.3)

        # Scatter plot
        axes[1].scatter(y_test, y_pred, alpha=0.5)
        axes[1].plot([y_test.min(), y_test.max()],
                     [y_test.min(), y_test.max()],
                     'r--', linewidth=2, label='Perfect Prediction')
        axes[1].set_xlabel('Actual Demand')
        axes[1].set_ylabel('Predicted Demand')
        axes[1].set_title('Prediction Accuracy')
        axes[1].legend()
        axes[1].grid(True, alpha=0.3)

        plt.tight_layout()
        plt.show()

    def plot_feature_importance(self, top_n=20):
        """
        Plot feature importance
        """

        top_features = self.feature_importance.head(top_n)

        plt.figure(figsize=(10, 8))
        plt.barh(range(len(top_features)), top_features['importance'])
        plt.yticks(range(len(top_features)), top_features['feature'])
        plt.xlabel('Importance')
        plt.title(f'Top {top_n} Feature Importance')
        plt.gca().invert_yaxis()
        plt.tight_layout()
        plt.show()

# Example usage
forecaster = MLDemandForecaster(model_type='xgboost')

# Prepare data
X_train, X_test, y_train, y_test, feature_cols = forecaster.prepare_data(
    features_df,
    test_size=90
)

# Train model
forecaster.train(X_train, y_train)

# Evaluate
metrics, y_pred = forecaster.evaluate(X_test, y_test)

print("\nModel Performance:")
for metric, value in metrics.items():
    print(f"{metric}: {value:.2f}")

# Visualize
test_dates = features_df[features_df['date'] > features_df['date'].max() - timedelta(days=90)]['date']
forecaster.plot_results(y_test, y_pred, dates=test_dates)
forecaster.plot_feature_importance(top_n=20)
LSTM によるディープラーニング
import tensorflow as tf
from tensorflow import keras
from tensorflow.keras import layers
from sklearn.preprocessing import StandardScaler

class LSTMForecaster:
    """
    LSTM neural network for demand forecasting
    """

    def __init__(self, lookback=30, forecast_horizon=7):
        """
        lookback: number of historical time steps to use
        forecast_horizon: number of future steps to predict
        """
        self.lookback = lookback
        self.forecast_horizon = forecast_horizon
        self.model = None
        self.scaler = StandardScaler()

    def create_sequences(self, data, target):
        """
        Create sequences for LSTM training

        data: feature array
        target: target array
        """
        X, y = [], []

        for i in range(len(data) - self.lookback - self.forecast_horizon + 1):
            X.append(data[i:i + self.lookback])
            y.append(target[i + self.lookback:i + self.lookback + self.forecast_horizon])

        return np.array(X), np.array(y)

    def build_model(self, n_features):
        """
        Build LSTM architecture
        """

        model = keras.Sequential([
            # First LSTM layer
            layers.LSTM(128, activation='relu',
                       return_sequences=True,
                       input_shape=(self.lookback, n_features)),
            layers.Dropout(0.2),

            # Second LSTM layer
            layers.LSTM(64, activation='relu',
                       return_sequences=True),
            layers.Dropout(0.2),

            # Third LSTM layer
            layers.LSTM(32, activation='relu'),
            layers.Dropout(0.2),

            # Dense layers
            layers.Dense(32, activation='relu'),
            layers.Dense(self.forecast_horizon)
        ])

        model.compile(
            optimizer=keras.optimizers.Adam(learning_rate=0.001),
            loss='mse',
            metrics=['mae']
        )

        return model

    def train(self, train_data, train_target, val_split=0.2, epochs=100, batch_size=32):
        """
        Train LSTM model
        """

        # Scale data
        train_data_scaled = self.scaler.fit_transform(train_data)

        # Create sequences
        X_seq, y_seq = self.create_sequences(train_data_scaled, train_target)

        print(f"Sequence shape: X={X_seq.shape}, y={y_seq.shape}")

        # Build model
        self.model = self.build_model(n_features=train_data.shape[1])

        # Callbacks
        early_stopping = keras.callbacks.EarlyStopping(
            monitor='val_loss',
            patience=15,
            restore_best_weights=True
        )

        reduce_lr = keras.callbacks.ReduceLROnPlateau(
            monitor='val_loss',
            factor=0.5,
            patience=5,
            min_lr=0.00001
        )

        # Train
        history = self.model.fit(
            X_seq, y_seq,
            validation_split=val_split,
            epochs=epochs,
            batch_size=batch_size,
            callbacks=[early_stopping, reduce_lr],
            verbose=1
        )

        return history

    def predict(self, data):
        """
        Make predictions
        """

        # Scale data
        data_scaled = self.scaler.transform(data)

        # Create sequences
        X_seq, _ = self.create_sequences(
            data_scaled,
            np.zeros(len(data))  # Dummy target
        )

        # Predict
        predictions = self.model.predict(X_seq)

        return predictions

# Example usage
lstm_forecaster = LSTMForecaster(lookback=30, forecast_horizon=7)

# Prepare data for LSTM
feature_cols = [col for col in features_df.columns
               if col not in ['date', 'sku', 'demand']]

train_data = features_df[feature_cols].values[:900]
train_target = features_df['demand'].values[:900]

# Train
history = lstm_forecaster.train(train_data, train_target, epochs=50)

# Plot training history
plt.figure(figsize=(12, 4))
plt.subplot(1, 2, 1)
plt.plot(history.history['loss'], label='Training Loss')
plt.plot(history.history['val_loss'], label='Validation Loss')
plt.title('Model Loss')
plt.xlabel('Epoch')
plt.ylabel('Loss')
plt.legend()

plt.subplot(1, 2, 2)
plt.plot(history.history['mae'], label='Training MAE')
plt.plot(history.history['val_mae'], label='Validation MAE')
plt.title('Model MAE')
plt.xlabel('Epoch')
plt.ylabel('MAE')
plt.legend()

plt.tight_layout()
plt.show()

分類の課題

出荷遅延の予測
from sklearn.ensemble import RandomForestClassifier
from sklearn.metrics import classification_report, confusion_matrix, roc_auc_score
import seaborn as sns

class ShipmentDelayPredictor:
    """
    Predict whether shipment will be delayed
    """

    def __init__(self):
        self.model = None
        self.feature_cols = None

    def prepare_features(self, shipments_df):
        """
        Create features for delay prediction

        shipments_df: columns = ['shipment_id', 'origin', 'destination',
                                 'carrier', 'mode', 'distance', 'weight',
                                 'weather_conditions', 'ship_date', 'promised_date',
                                 'actual_delivery_date']
        """

        df = shipments_df.copy()

        # Create target: delayed = 1, on-time = 0
        df['delayed'] = (df['actual_delivery_date'] > df['promised_date']).astype(int)

        # Temporal features
        df['ship_day_of_week'] = df['ship_date'].dt.dayofweek
        df['ship_month'] = df['ship_date'].dt.month
        df['is_weekend'] = (df['ship_day_of_week'] >= 5).astype(int)

        # Transit features
        df['promised_transit_days'] = (df['promised_date'] - df['ship_date']).dt.days
        df['distance_per_day'] = df['distance'] / df['promised_transit_days']

        # Weight categories
        df['weight_category'] = pd.cut(
            df['weight'],
            bins=[0, 1000, 5000, 15000, float('inf')],
            labels=['light', 'medium', 'heavy', 'very_heavy']
        )

        # Carrier historical performance (would need historical data)
        # For demo, create dummy
        df['carrier_reliability'] = df['carrier'].map({
            'Carrier_A': 0.95,
            'Carrier_B': 0.88,
            'Carrier_C': 0.92
        }).fillna(0.90)

        # Weather conditions (categorical)
        df['bad_weather'] = df['weather_conditions'].isin(['rain', 'snow', 'storm']).astype(int)

        # One-hot encode categorical variables
        df_encoded = pd.get_dummies(
            df,
            columns=['origin', 'destination', 'carrier', 'mode', 'weight_category'],
            drop_first=True
        )

        return df_encoded

    def train(self, X_train, y_train):
        """
        Train random forest classifier
        """

        self.feature_cols = X_train.columns

        self.model = RandomForestClassifier(
            n_estimators=200,
            max_depth=10,
            min_samples_split=20,
            min_samples_leaf=10,
            class_weight='balanced',  # Handle imbalanced classes
            random_state=42,
            n_jobs=-1
        )

        self.model.fit(X_train, y_train)

        # Feature importance
        self.feature_importance = pd.DataFrame({
            'feature': self.feature_cols,
            'importance': self.model.feature_importances_
        }).sort_values('importance', ascending=False)

    def predict(self, X_test):
        """
        Predict delay probability
        """
        return self.model.predict(X_test)

    def predict_proba(self, X_test):
        """
        Predict delay probability
        """
        return self.model.predict_proba(X_test)[:, 1]

    def evaluate(self, X_test, y_test):
        """
        Evaluate classifier performance
        """

        y_pred = self.predict(X_test)
        y_pred_proba = self.predict_proba(X_test)

        print("Classification Report:")
        print(classification_report(y_test, y_pred))

        print(f"\nROC-AUC Score: {roc_auc_score(y_test, y_pred_proba):.3f}")

        # Confusion matrix
        cm = confusion_matrix(y_test, y_pred)

        plt.figure(figsize=(8, 6))
        sns.heatmap(cm, annot=True, fmt='d', cmap='Blues')
        plt.title('Confusion Matrix')
        plt.ylabel('Actual')
        plt.xlabel('Predicted')
        plt.show()

        return {
            'accuracy': (y_pred == y_test).mean(),
            'roc_auc': roc_auc_score(y_test, y_pred_proba)
        }

# Example usage
delay_predictor = ShipmentDelayPredictor()

# Prepare data
shipments_encoded = delay_predictor.prepare_features(shipments_df)

# Split
feature_cols = [col for col in shipments_encoded.columns
               if col not in ['shipment_id', 'ship_date', 'promised_date',
                             'actual_delivery_date', 'delayed']]

X = shipments_encoded[feature_cols]
y = shipments_encoded['delayed']

# Train/test split
split_idx = int(len(X) * 0.8)
X_train, X_test = X[:split_idx], X[split_idx:]
y_train, y_test = y[:split_idx], y[split_idx:]

# Train
delay_predictor.train(X_train, y_train)

# Evaluate
metrics = delay_predictor.evaluate(X_test, y_test)

クラスタリングとセグメンテーション

顧客セグメンテーション
from sklearn.cluster import KMeans
from sklearn.preprocessing import StandardScaler
from sklearn.decomposition import PCA
import matplotlib.pyplot as plt

class CustomerSegmentation:
    """
    Segment customers using clustering
    """

    def __init__(self, n_clusters=4):
        self.n_clusters = n_clusters
        self.scaler = StandardScaler()
        self.pca = PCA(n_components=2)
        self.kmeans = None

    def create_customer_features(self, transactions_df):
        """
        Create RFM and other features for segmentation

        transactions_df: columns = ['customer_id', 'order_date',
                                    'order_value', 'quantity']
        """

        # Calculate RFM metrics
        reference_date = transactions_df['order_date'].max()

        rfm = transactions_df.groupby('customer_id').agg({
            'order_date': lambda x: (reference_date - x.max()).days,  # Recency
            'customer_id': 'count',  # Frequency
            'order_value': 'sum'  # Monetary
        })

        rfm.columns = ['recency', 'frequency', 'monetary']

        # Additional features
        customer_features = transactions_df.groupby('customer_id').agg({
            'order_value': ['mean', 'std', 'min', 'max'],
            'quantity': ['mean', 'sum']
        })

        customer_features.columns = [
            'avg_order_value', 'std_order_value', 'min_order_value', 'max_order_value',
            'avg_quantity', 'total_quantity'
        ]

        # Combine
        features = rfm.join(customer_features)

        return features

    def fit(self, features_df):
        """
        Fit clustering model
        """

        # Scale features
        features_scaled = self.scaler.fit_transform(features_df)

        # Fit K-means
        self.kmeans = KMeans(
            n_clusters=self.n_clusters,
            random_state=42,
            n_init=10
        )

        clusters = self.kmeans.fit_predict(features_scaled)

        # Add cluster labels
        features_df['cluster'] = clusters

        return features_df

    def visualize_clusters(self, features_df):
        """
        Visualize clusters using PCA
        """

        # Get features without cluster column
        feature_cols = [col for col in features_df.columns if col != 'cluster']
        X = features_df[feature_cols]

        # Scale and apply PCA
        X_scaled = self.scaler.transform(X)
        X_pca = self.pca.fit_transform(X_scaled)

        # Plot
        plt.figure(figsize=(12, 8))

        for cluster in range(self.n_clusters):
            mask = features_df['cluster'] == cluster
            plt.scatter(
                X_pca[mask, 0],
                X_pca[mask, 1],
                label=f'Cluster {cluster}',
                s=100,
                alpha=0.6
            )

        # Plot centroids
        centroids_scaled = self.kmeans.cluster_centers_
        centroids_pca = self.pca.transform(centroids_scaled)

        plt.scatter(
            centroids_pca[:, 0],
            centroids_pca[:, 1],
            marker='X',
            s=500,
            c='red',
            edgecolors='black',
            linewidths=2,
            label='Centroids'
        )

        plt.xlabel('First Principal Component')
        plt.ylabel('Second Principal Component')
        plt.title('Customer Segmentation')
        plt.legend()
        plt.grid(True, alpha=0.3)
        plt.show()

    def profile_clusters(self, features_df):
        """
        Create cluster profiles
        """

        # Calculate mean for each cluster
        profiles = features_df.groupby('cluster').mean()

        # Add cluster size
        profiles['size'] = features_df.groupby('cluster').size()

        # Name clusters based on characteristics
        def name_cluster(row):
            if row['frequency'] > profiles['frequency'].median() and \
               row['monetary'] > profiles['monetary'].median():
                return 'Champions'
            elif row['recency'] < profiles['recency'].median() and \
                 row['frequency'] > profiles['frequency'].median():
                return 'Loyal'
            elif row['recency'] > profiles['recency'].median() and \
                 row['frequency'] < profiles['frequency'].median():
                return 'At Risk'
            else:
                return 'Potential'

        profiles['segment_name'] = profiles.apply(name_cluster, axis=1)

        return profiles

# Example usage
segmentation = CustomerSegmentation(n_clusters=4)

# Create features
customer_features = segmentation.create_customer_features(transactions_df)

# Fit clustering
clustered_customers = segmentation.fit(customer_features)

# Visualize
segmentation.visualize_clusters(clustered_customers)

# Profile clusters
cluster_profiles = segmentation.profile_clusters(clustered_customers)
print(cluster_profiles)

サプライチェーンのコンピュータービジョン

CNN による品質検査
import tensorflow as tf
from tensorflow.keras import layers, models
from tensorflow.keras.preprocessing.image import ImageDataGenerator

class QualityInspectionModel:
    """
    Convolutional Neural Network for quality defect detection
    """

    def __init__(self, image_size=(224, 224), num_classes=2):
        """
        image_size: input image dimensions
        num_classes: number of defect categories (e.g., good, defective)
        """
        self.image_size = image_size
        self.num_classes = num_classes
        self.model = None

    def build_model(self):
        """
        Build CNN architecture
        """

        model = models.Sequential([
            # Convolutional layers
            layers.Conv2D(32, (3, 3), activation='relu',
                         input_shape=(*self.image_size, 3)),
            layers.MaxPooling2D((2, 2)),

            layers.Conv2D(64, (3, 3), activation='relu'),
            layers.MaxPooling2D((2, 2)),

            layers.Conv2D(128, (3, 3), activation='relu'),
            layers.MaxPooling2D((2, 2)),

            layers.Conv2D(128, (3, 3), activation='relu'),
            layers.MaxPooling2D((2, 2)),

            # Flatten and dense layers
            layers.Flatten(),
            layers.Dropout(0.5),
            layers.Dense(512, activation='relu'),
            layers.Dropout(0.5),
            layers.Dense(self.num_classes, activation='softmax')
        ])

        model.compile(
            optimizer='adam',
            loss='categorical_crossentropy',
            metrics=['accuracy']
        )

        return model

    def use_transfer_learning(self, base_model_name='VGG16'):
        """
        Use pre-trained model with transfer learning
        """

        # Load pre-trained model
        if base_model_name == 'VGG16':
            base_model = tf.keras.applications.VGG16(
                input_shape=(*self.image_size, 3),
                include_top=False,
                weights='imagenet'
            )
        elif base_model_name == 'ResNet50':
            base_model = tf.keras.applications.ResNet50(
                input_shape=(*self.image_size, 3),
                include_top=False,
                weights='imagenet'
            )

        # Freeze base model
        base_model.trainable = False

        # Add custom top layers
        model = models.Sequential([
            base_model,
            layers.GlobalAveragePooling2D(),
            layers.Dense(256, activation='relu'),
            layers.Dropout(0.5),
            layers.Dense(self.num_classes, activation='softmax')
        ])

        model.compile(
            optimizer=tf.keras.optimizers.Adam(learning_rate=0.0001),
            loss='categorical_crossentropy',
            metrics=['accuracy']
        )

        return model

    def train(self, train_dir, val_dir, epochs=50, batch_size=32,
             use_transfer_learning=True):
        """
        Train model on image data

        train_dir: directory with training images organized by class
        val_dir: directory with validation images
        """

        # Data augmentation
        train_datagen = ImageDataGenerator(
            rescale=1./255,
            rotation_range=20,
            width_shift_range=0.2,
            height_shift_range=0.2,
            horizontal_flip=True,
            fill_mode='nearest'
        )

        val_datagen = ImageDataGenerator(rescale=1./255)

        # Load data
        train_generator = train_datagen.flow_from_directory(
            train_dir,
            target_size=self.image_size,
            batch_size=batch_size,
            class_mode='categorical'
        )

        val_generator = val_datagen.flow_from_directory(
            val_dir,
            target_size=self.image_size,
            batch_size=batch_size,
            class_mode='categorical'
        )

        # Build model
        if use_transfer_learning:
            self.model = self.use_transfer_learning()
        else:
            self.model = self.build_model()

        # Callbacks
        early_stopping = tf.keras.callbacks.EarlyStopping(
            monitor='val_loss',
            patience=10,
            restore_best_weights=True
        )

        model_checkpoint = tf.keras.callbacks.ModelCheckpoint(
            'best_quality_model.h5',
            monitor='val_accuracy',
            save_best_only=True
        )

        # Train
        history = self.model.fit(
            train_generator,
            epochs=epochs,
            validation_data=val_generator,
            callbacks=[early_stopping, model_checkpoint]
        )

        return history

    def predict_defects(self, image_path):
        """
        Predict defects in single image
        """

        # Load and preprocess image
        img = tf.keras.preprocessing.image.load_img(
            image_path,
            target_size=self.image_size
        )
        img_array = tf.keras.preprocessing.image.img_to_array(img)
        img_array = np.expand_dims(img_array, axis=0)
        img_array /= 255.0

        # Predict
        predictions = self.model.predict(img_array)

        return predictions[0]

# Example usage
quality_model = QualityInspectionModel(image_size=(224, 224), num_classes=2)

# Train model
# history = quality_model.train(
#     train_dir='data/train',
#     val_dir='data/val',
#     epochs=30,
#     use_transfer_learning=True
# )

# Predict on new image
# predictions = quality_model.predict_defects('path/to/product_image.jpg')
# print(f"Defect probability: {predictions[1]:.2%}")

自然言語処理

ニュースからのサプライヤーリスク分析
from transformers import pipeline, AutoTokenizer, AutoModelForSequenceClassification
import requests

class SupplierRiskNLP:
    """
    Analyze supplier risk from news articles and reports
    """

    def __init__(self):
        # Load sentiment analysis model
        self.sentiment_analyzer = pipeline(
            "sentiment-analysis",
            model="distilbert-base-uncased-finetuned-sst-2-english"
        )

        # Load NER model for entity extraction
        self.ner_analyzer = pipeline(
            "ner",
            model="dbmdz/bert-large-cased-finetuned-conll03-english",
            aggregation_strategy="simple"
        )

    def fetch_news(self, supplier_name, api_key):
        """
        Fetch news articles about supplier

        Uses News API (newsapi.org)
        """

        url = 'https://newsapi.org/v2/everything'
        params = {
            'q': supplier_name,
            'apiKey': api_key,
            'language': 'en',
            'sortBy': 'publishedAt',
            'pageSize': 100
        }

        response = requests.get(url, params=params)

        if response.status_code == 200:
            articles = response.json()['articles']
            return articles
        else:
            return []

    def analyze_sentiment(self, text):
        """
        Analyze sentiment of text
        """

        # Split long text into chunks (model has token limit)
        max_length = 512
        chunks = [text[i:i+max_length] for i in range(0, len(text), max_length)]

        sentiments = []
        for chunk in chunks:
            result = self.sentiment_analyzer(chunk)[0]
            sentiments.append(result)

        # Aggregate sentiments
        positive_count = sum(1 for s in sentiments if s['label'] == 'POSITIVE')
        avg_score = np.mean([s['score'] for s in sentiments])

        overall = {
            'positive_ratio': positive_count / len(sentiments),
            'average_confidence': avg_score,
            'risk_indicator': 1 - (positive_count / len(sentiments))
        }

        return overall

    def extract_risk_indicators(self, articles):
        """
        Extract risk-related keywords and entities
        """

        risk_keywords = [
            'bankruptcy', 'lawsuit', 'recall', 'defect', 'violation',
            'investigation', 'scandal', 'delay', 'shortage', 'disruption',
            'closure', 'layoff', 'protest', 'strike'
        ]

        risk_score = 0
        risk_events = []

        for article in articles:
            text = article.get('title', '') + ' ' + article.get('description', '')
            text_lower = text.lower()

            # Check for risk keywords
            for keyword in risk_keywords:
                if keyword in text_lower:
                    risk_score += 1
                    risk_events.append({
                        'keyword': keyword,
                        'title': article.get('title'),
                        'date': article.get('publishedAt'),
                        'url': article.get('url')
                    })

        return risk_score, risk_events

    def assess_supplier_risk(self, supplier_name, api_key):
        """
        Complete supplier risk assessment
        """

        print(f"Assessing risk for supplier: {supplier_name}")

        # Fetch news
        articles = self.fetch_news(supplier_name, api_key)
        print(f"Found {len(articles)} articles")

        if not articles:
            return {
                'supplier': supplier_name,
                'risk_level': 'Unknown',
                'score': 0,
                'message': 'No articles found'
            }

        # Analyze sentiment
        all_text = ' '.join([
            art.get('title', '') + ' ' + art.get('description', '')
            for art in articles
        ])

        sentiment = self.analyze_sentiment(all_text)

        # Extract risk indicators
        risk_score, risk_events = self.extract_risk_indicators(articles)

        # Calculate overall risk
        # Higher risk_indicator and higher risk_score = higher risk
        overall_risk_score = (sentiment['risk_indicator'] * 0.6 +
                             min(risk_score / len(articles), 1.0) * 0.4)

        if overall_risk_score > 0.7:
            risk_level = 'HIGH'
        elif overall_risk_score > 0.4:
            risk_level = 'MEDIUM'
        else:
            risk_level = 'LOW'

        return {
            'supplier': supplier_name,
            'risk_level': risk_level,
            'risk_score': overall_risk_score,
            'sentiment_analysis': sentiment,
            'risk_keyword_count': risk_score,
            'risk_events': risk_events[:5],  # Top 5 events
            'articles_analyzed': len(articles)
        }

# Example usage
risk_analyzer = SupplierRiskNLP()

# Assess supplier risk
# assessment = risk_analyzer.assess_supplier_risk('Acme Corp', api_key='your_api_key')
# print(f"Risk Level: {assessment['risk_level']}")
# print(f"Risk Score: {assessment['risk_score']:.2f}")

MLOps とモデルのデプロイ

FastAPI によるモデルのデプロイ
from fastapi import FastAPI, HTTPException
from pydantic import BaseModel
import joblib
import numpy as np
import uvicorn

app = FastAPI(title="Supply Chain ML API")

# Load pre-trained model
demand_model = joblib.load('demand_forecast_model.pkl')
delay_model = joblib.load('delay_prediction_model.pkl')

# Request models
class DemandForecastRequest(BaseModel):
    sku: str
    features: dict  # Feature dictionary

class DelayPredictionRequest(BaseModel):
    shipment_features: dict

# Response models
class DemandForecastResponse(BaseModel):
    sku: str
    predicted_demand: float
    confidence_interval_lower: float
    confidence_interval_upper: float

class DelayPredictionResponse(BaseModel):
    delay_probability: float
    predicted_class: str
    confidence: float

@app.post("/api/v1/forecast/demand", response_model=DemandForecastResponse)
async def forecast_demand(request: DemandForecastRequest):
    """
    Forecast demand for a SKU
    """
    try:
        # Extract features
        features_array = np.array(list(request.features.values())).reshape(1, -1)

        # Predict
        prediction = demand_model.predict(features_array)[0]

        # Calculate confidence interval (simplified)
        std_dev = prediction * 0.15  # Assume 15% std dev
        ci_lower = max(0, prediction - 1.96 * std_dev)
        ci_upper = prediction + 1.96 * std_dev

        return DemandForecastResponse(
            sku=request.sku,
            predicted_demand=float(prediction),
            confidence_interval_lower=float(ci_lower),
            confidence_interval_upper=float(ci_upper)
        )

    except Exception as e:
        raise HTTPException(status_code=500, detail=str(e))

@app.post("/api/v1/predict/delay", response_model=DelayPredictionResponse)
async def predict_delay(request: DelayPredictionRequest):
    """
    Predict shipment delay probability
    """
    try:
        # Extract features
        features_array = np.array(
            list(request.shipment_features.values())
        ).reshape(1, -1)

        # Predict probability
        delay_proba = delay_model.predict_proba(features_array)[0][1]
        predicted_class = "Delayed" if delay_proba > 0.5 else "On-Time"
        confidence = max(delay_proba, 1 - delay_proba)

        return DelayPredictionResponse(
            delay_probability=float(delay_proba),
            predicted_class=predicted_class,
            confidence=float(confidence)
        )

    except Exception as e:
        raise HTTPException(status_code=500, detail=str(e))

@app.get("/health")
async def health_check():
    """Health check endpoint"""
    return {"status": "healthy"}

# Run with: uvicorn api:app --reload

ツールとライブラリ

Python の ML ライブラリ

コア ML:

  • scikit-learn:従来型の ML アルゴリズム
  • xgboost:勾配ブースティング
  • lightgbm:高速な勾配ブースティング
  • catboost:カテゴリデータ向けの勾配ブースティング

ディープラーニング:

  • tensorflow/keras:ニューラルネットワーク
  • pytorch:動的なニューラルネットワーク
  • fastai:高水準のディープラーニング

時系列:

  • prophet:時系列予測
  • statsmodels:統計モデル
  • sktime:時系列 ML
  • darts:時系列予測ライブラリ

NLP:

  • transformers (Hugging Face):事前学習済み言語モデル
  • spacy:産業向け NLP
  • nltk:自然言語ツールキット

コンピュータービジョン:

  • opencv:コンピュータービジョン
  • PIL/pillow:画像処理
  • albumentations:画像の拡張

モデルのデプロイ:

  • fastapi:API フレームワーク
  • flask:ウェブフレームワーク
  • streamlit:データアプリ
  • mlflow:ML ライフサイクル管理
  • kubeflow:Kubernetes 上の ML
クラウド ML プラットフォーム

トレーニングとデプロイ:

  • AWS SageMaker:エンドツーエンドの ML プラットフォーム
  • Google Cloud AI Platform:GCP 上の ML
  • Azure Machine Learning:Microsoft の ML プラットフォーム
  • Databricks:統合分析プラットフォーム

MLOps:

  • MLflow:オープンソースの ML ライフサイクル管理
  • Weights & Biases:実験の追跡
  • Neptune.ai:ML メタデータストア
  • DVC:データのバージョン管理

よくある課題と解決策

課題:トレーニングデータが不足している

問題:

  • ML には大規模なデータセット(1000 件以上のサンプル)が必要です
  • サプライチェーンのデータは限られていることがよくあります

解決策:

  • 事前トレーニング済みモデルを使った転移学習を利用します
  • データ拡張の手法を活用します
  • 合成データを生成します
  • より単純なモデル(パラメーター数の少ないモデル)から始めます
  • 複数のデータソースを組み合わせます
  • 半教師あり学習または能動学習を利用します
課題:クラスの不均衡

問題:

  • まれな事象(欠陥、遅延)の例が少ないです
  • モデルが多数派クラスに偏ります

解決策:

  • 損失関数でクラスの重み付けを行います
  • 少数派クラスをオーバーサンプリングします(SMOTE)
  • 多数派クラスをアンダーサンプリングします
  • アンサンブル手法を利用します
  • 異常検知のアプローチを利用します
  • 適切な指標(適合率、再現率、F1。正解率だけではありません)を使用します
課題:モデルの解釈可能性

問題:

  • ブラックボックスモデルは説明が困難です
  • ビジネス上、予測内容を理解する必要があります

解決策:

  • 解釈可能なモデル(線形モデル、決定木ベースのモデル)を利用します
  • 特徴量の重要度を把握するために SHAP 値を使用します
  • 局所的な説明には LIME を使用します
  • 部分依存プロットを使用します
  • 特徴量の重要度を分析します
  • モデルの文書化と検証を行います
課題:概念ドリフト

問題:

  • サプライチェーンのパターンは時間とともに変化します
  • モデルの性能が低下します

解決策:

  • モデルの性能を継続的に監視します
  • モデルを定期的に再トレーニングします(月次、四半期ごと)
  • オンライン学習のアプローチを利用します
  • 統計的検定でドリフトを検出します
  • モデル更新の A/B テストを実施します
  • 複数のモデルバージョンを維持します

出力形式

ML プロジェクトレポートのテンプレート

エグゼクティブサマリー:

  • 対象とするビジネス課題
  • 採用した ML アプローチ
  • 主な結果と指標
  • 推奨アクション

問題の定義:

  • 詳細な問題の説明
  • 定義した成功指標
  • 現在のベースライン性能

データ分析:

  • データソースとデータ量
  • 特徴量エンジニアリングのアプローチ
  • データ品質の評価
  • 探索的データ分析

モデル開発:

モデルアルゴリズムトレーニング精度テスト精度MAERMSE
ベースライン移動平均--45.262.3
モデル 1Random Forest0.920.8732.144.5
モデル 2XGBoost0.950.8928.339.7
モデル 3LSTM0.940.9026.837.2

モデル性能:

  • 選択したモデルとその根拠
  • 性能指標
  • 特徴量の重要度
  • 誤差分析

デプロイ計画:

  • デプロイアーキテクチャ
  • API 仕様
  • 監視戦略
  • 再トレーニングのスケジュール

ビジネスへの影響:

  • 期待される改善
  • 費用対効果分析
  • 導入ロードマップ
  • リスクと軽減策

確認すべき質問

さらに詳しい情報が必要な場合:

  1. ML で解決する必要がある具体的なサプライチェーンの課題は何ですか?
  2. どのようなデータを利用できますか?(量、特徴量、品質)
  3. 予測対象となる目的変数または結果は何ですか?
  4. これは教師あり学習、教師なし学習、それとも強化学習ですか?
  5. 現在のベースライン性能はどの程度ですか?
  6. ビジネス上の価値を得るには、どの程度の精度が必要ですか?
  7. デプロイ要件は何ですか?(バッチ、リアルタイム、エッジ)
  8. チームにはどのような ML の専門知識がありますか?

関連スキル

  • demand-forecasting:時系列予測の手法
  • supply-chain-analytics:KPI と指標
  • optimization-modeling:ML と最適化の組み合わせ
  • prescriptive-analytics:ML による意思決定支援
  • digital-twin-modeling:ML を用いたシミュレーション
  • computer-vision-warehouse:倉庫での CV アプリケーション
  • nlp-supply-chain:NLP に特化したアプリケーション
  • neural-networks-forecasting:深層学習による予測

コメント