100 Exercises / Probability Statistics / 100 Exercise Points in Probability & Statistical Marketing Applications

Hands-on Machine Learning in Manufacturing with Python | 10 Exercise-Down Prediction, Causal Inference, and Optimization

From Prediction to On-Site Action: 10 Key Exercises on Machine Learning in Manufacturing

This article uses a fictional industrial equipment manufacturer as a subject to connect Regression, classification, clustering, dimensionality reduction, anomaly detection, time series prediction, causal inference, Bayesian optimization, reinforcement learning, graph machine learning to decision-making in manufacturing.

The goal is not to compete in model accuracy. We specify which data to use, which metrics to use, and which metrics to use, and to support whose decisions are supported, including labor estimates, quality assessments, customer interactions, equipment monitoring, demand planning, improvement measures, processing conditions, maintenance policies, and supplier risks. All data is generated in Python and does not depend on external data.

[!NOTE] This material is a notebook previously used by Surikoubo (or personally by the representative, Kazuyama), and has been reconstructed, edited, and published with the company’s permission. All data listed is fictional and has no relation whatsoever to real companies, factories, or figures.

Introduction: Practical Challenges in Manufacturing Covered in This Article

Target companies process and assemble pump parts according to order specifications and sell them both domestically and internationally. On-site challenges include errors in machining time estimates, missed defects, equipment downtime, demand fluctuations, overestimation of improvement effects, and the spread of supplier disruptions.

Machine learning is not magic that automatically solves these problems. Value is created only by converting forecasts into decisions such as “approve overtime,” “add inspections,” or “implement maintenance,” and only by monitoring the gap with actual results can value be realized. This article explains the outputs of each model in pairs with the tasks that use those outputs.

Common situations on site

  • Standard man-hours are only averages by product group, and delivery deadlines are missed for high-difficulty items.
  • We created a defect prediction model but did not compare the costs of missed or over-tested
  • There are many sensor items, and it’s unclear which changes to indicate to equipment personnel.
  • Simply comparing the average before and after improvements can confuse project difficulty and equipment differences with improvement effects.
  • Prediction and optimization results do not connect to existing work standards, approval permissions, or exception handling

Why is this issue so difficult to judge?

Manufacturing data depends on the time series, equipment, product types, workers, and business partners. With random partitioning, future information is mixed into the learning side, and average accuracy alone can hide missed major defects. Also, even if the correlation is high, it does not necessarily mean the effect is due to changing conditions.

Therefore, the objective variable, the explanatory variables available at the time of forecasting, evaluation units, misjudgment costs, and scope of application are defined first. The choice of model is the next step.

Overview of Exercise covered this time

No.ThemeKey Challenges in ManufacturingMain Evaluations and Outputs
061ReturnHow many minutes should you estimate the processing time?MAE, residual
062CategoriesShould we conduct additional inspections for defective risk products?Recall rate, mixed lineup
063clusteringHow to Divide Customers into Support PoliciesSegment Characteristics
064Dimension reductionHow to summarize numerous sensor changesContribution Rate and Principal Component Scores
065anomaly detectionHow to pick up equipment abnormalities with few correct labelsAbnormal scores, top candidates
066Time Series ForecastHere are some of the order volumes and required capabilities for next month.Time-Series MAE, Forecast Trends
067causal inferenceDid the new jig really shorten processing time?ATE by IPW
068Bayesian optimizationHow to find machining conditions with minimal prototypingAcquired Function, Best Condition
069Reinforcement LearningWhether to preserve or continue for each deterioration conditionStrategies, cumulative rewards
070Graph Machine LearningHow to view supply risk from the perspective of the trading networkProximity Features, Risk Probability

Preparing the Python environment

NumPy and pandas are used for data generation and aggregation, scikit-learn for preprocessing and machine learning, SciPy for earning functions, NetworkX for transaction networks, and matplotlib for visualization. Use default_rng(42) to rerun it so that the same fictitious data is used.

%matplotlib inline
%config InlineBackend.figure_format = 'svg'

import warnings
warnings.filterwarnings("ignore")

import numpy as np
import pandas as pd
import matplotlib
import matplotlib.pyplot as plt
import japanize_matplotlib
import networkx as nx
import sklearn

from IPython.display import display
from scipy.stats import norm
from sklearn.cluster import KMeans
from sklearn.compose import ColumnTransformer
from sklearn.decomposition import PCA
from sklearn.ensemble import IsolationForest
from sklearn.gaussian_process import GaussianProcessRegressor
from sklearn.gaussian_process.kernels import ConstantKernel, Matern, WhiteKernel
from sklearn.linear_model import LinearRegression, LogisticRegression
from sklearn.metrics import (ConfusionMatrixDisplay, RocCurveDisplay, accuracy_score,
                             classification_report, mean_absolute_error, r2_score,
                             roc_auc_score)
from sklearn.model_selection import train_test_split
from sklearn.pipeline import make_pipeline
from sklearn.preprocessing import OneHotEncoder, StandardScaler

rng = np.random.default_rng(42)
print("numpy      :", np.__version__)
print("pandas     :", pd.__version__)
print("matplotlib :", matplotlib.__version__)
print("sklearn    :", sklearn.__version__)
print("networkx   :", nx.__version__)
numpy      : 2.5.1
pandas     : 3.0.3
matplotlib : 3.11.0
sklearn    : 1.9.0
networkx   : 3.6.1

Creation of Fictional Data

We assume 420 recent processing cases. In addition to materials that can be identified at the time of order, product line, lot size, complexity, equipment, and number of setups, we also provide vibration, temperature, dimensional deviations, and defect flags that can be identified after processing. event_date are arranged in chronological order so they can be used for chronological evaluation.

What matters is the “timing of prediction.” For example, when estimating labor hours at the time of order, vibration or dimensional deviations measured after processing cannot be used. Ignoring availability results in data leaks with high verification accuracy.

n = 420
event_date = pd.date_range("2025-01-01", periods=n, freq="D")
material = rng.choice(["Steel", "Aluminum", "Resin"], n, p=[0.50, 0.30, 0.20])
product = rng.choice(["Pump-A", "Pump-B", "Pump-C"], n, p=[0.45, 0.35, 0.20])
machine = rng.choice(["MC-1", "MC-2", "MC-3"], n, p=[0.40, 0.35, 0.25])
lot_size = rng.integers(20, 151, n)
complexity = np.clip(rng.normal(5.0, 1.8, n), 1, 10)
setup_count = rng.integers(1, 5, n)

mat_effect = pd.Series(material).map({"Steel": 18, "Aluminum": 8, "Resin": 3}).to_numpy()
prod_effect = pd.Series(product).map({"Pump-A": 0, "Pump-B": 12, "Pump-C": 25}).to_numpy()
machine_effect = pd.Series(machine).map({"MC-1": 2, "MC-2": -4, "MC-3": 6}).to_numpy()
cycle_time = (35 + 0.48 * lot_size + 7.2 * complexity + 5.5 * setup_count
              + mat_effect + prod_effect + machine_effect + rng.normal(0, 8, n))
vibration = np.clip(1.6 + 0.12 * complexity + 0.003 * lot_size
                    + (machine == "MC-3") * 0.35 + rng.normal(0, 0.22, n), 0.5, None)
temperature = 53 + 1.7 * complexity + (machine == "MC-1") * 3 + rng.normal(0, 3.2, n)
dim_error = rng.normal(0, 0.018 + 0.003 * complexity, n) + (vibration - 2.4) * 0.012
logit = -5.0 + 0.75 * complexity + 0.85 * (vibration - 2.2) + 18 * np.abs(dim_error)
defect_prob = 1 / (1 + np.exp(-logit))
defect = rng.binomial(1, defect_prob)

production = pd.DataFrame({
    "event_date": event_date, "material": material, "product": product,
    "machine": machine, "lot_size": lot_size, "complexity": complexity.round(2),
    "setup_count": setup_count, "cycle_time_min": cycle_time.round(1),
    "vibration_mm_s": vibration.round(3), "temperature_c": temperature.round(2),
    "dimension_error_mm": dim_error.round(4), "defect": defect,
})
display(production.head())
print(f"Number of lines: {len(production):,} / non_performing_rate: {production['defect'].mean():.1%}")
event_date material product machine lot_size complexity setup_count cycle_time_min vibration_mm_s temperature_c dimension_error_mm defect
0 2025-01-01 Aluminum Pump-B MC-1 77 4.51 1 127.1 2.540 60.91 0.0375 0
1 2025-01-02 Steel Pump-A MC-3 25 3.33 1 91.7 2.363 61.24 0.0212 0
2 2025-01-03 Resin Pump-A MC-2 47 3.44 2 80.7 2.553 57.05 -0.0436 0
3 2025-01-04 Aluminum Pump-A MC-2 72 2.06 3 118.6 1.869 51.99 -0.0139 0
4 2025-01-05 Steel Pump-B MC-1 28 5.43 4 143.7 2.383 68.49 0.0055 0
Number of lines: 420 / Defect rate: 36.9%

No.061: Regression — Estimating machining time based on order specifications

Meaning in Practice

Forecasting machining time is a common input for estimated cost, delivery date response, equipment load, and overtime judgment. By reflecting not only the average standard time but also materials, types, equipment, and difficulty, you can reduce underestimation of high-difficulty projects.

Approach to Analysis and Modeling

Linear regression is used to predict continuous values yy.

y^=β0+j=1pβjxj\hat{y}=\beta_0+\sum_{j=1}^{p}\beta_jx_j

Category variables are one-hot, learning 80% of the first half of the time series and evaluating the second 20%. Errors are checked by the mean absolute error of MAE=n1yiy^i\mathrm{MAE}=n^{-1}\sum|y_i-\hat y_i|, which is easy to interpret on site, and the R2R^2, which represents the rate of explanation.

Check with Python

features_061 = ["material", "product", "machine", "lot_size", "complexity", "setup_count"]
cat_061 = ["material", "product", "machine"]
num_061 = ["lot_size", "complexity", "setup_count"]
split = int(len(production) * 0.8)
X_train, X_test = production.loc[:split-1, features_061], production.loc[split:, features_061]
y_train, y_test = production.loc[:split-1, "cycle_time_min"], production.loc[split:, "cycle_time_min"]

prep_061 = ColumnTransformer([
    ("category", OneHotEncoder(handle_unknown="ignore"), cat_061),
    ("numeric", StandardScaler(), num_061),
])
model_061 = make_pipeline(prep_061, LinearRegression()).fit(X_train, y_train)
pred_061 = model_061.predict(X_test)
metrics_061 = pd.DataFrame({
    "indicator": ["MAE(minutes)", "R²"],
    "value": [mean_absolute_error(y_test, pred_061), r2_score(y_test, pred_061)],
})
display(metrics_061.round(3))

fig, ax = plt.subplots(figsize=(7, 4))
ax.scatter(pred_061, y_test - pred_061, alpha=0.65)
ax.axhline(0, color="red", linestyle="--")
ax.set_title("Residue in Processing Time Prediction")
ax.set_xlabel("Estimated processing time (minutes)")
ax.set_ylabel("Achievements - Forecast (minutes)")
ax.grid(True, alpha=0.3)
plt.tight_layout()
plt.show()
indicator value
0 MAE(minutes) 6.898
1 0.902

svg

Reading the results

MAE shows how many minutes the estimate deviates on average. If the residuals are scattered around zero, there is no large systematic error, but if the residuals widen over the long term, it is necessary to subdivide the difficulty classification and predict sections. In practice, instead of using MAE as a delivery buffer, we check the underestimation rate by product group and the costs that underestimation results in overtime and delivery times.

No.062: Classification — Assigning Additional Tests Based on Defect Risk

Meaning in Practice

While full inspection is easier to maintain quality, it consumes inspection capacity and lead time. The classification model assigns lots with a high defect probability to key inspections, serving as a criterion for balancing oversight and inspection load.

Approach to Analysis and Modeling

Estimate the defect probability using logistic regression.

P(Y=1x)=11+exp[(β0+βTx)]P(Y=1\mid x)=\frac{1}{1+\exp[-(\beta_0+\beta^Tx)]}

Since defective classes are minor, use class_weight="balanced". Not only ROC-AUC but also mixed sequences are checked, and false negatives that are judged as normal are managed in particular. This time, assuming a quality gate after processing, vibration, temperature, and dimensional deviations are included in the input.

Check with Python

features_062 = ["complexity", "vibration_mm_s", "temperature_c", "dimension_error_mm"]
X_062 = production[features_062]
y_062 = production["defect"]
X_train, X_test, y_train, y_test = train_test_split(
    X_062, y_062, test_size=0.25, random_state=42, stratify=y_062
)
model_062 = make_pipeline(
    StandardScaler(), LogisticRegression(class_weight="balanced", random_state=42)
).fit(X_train, y_train)
prob_062 = model_062.predict_proba(X_test)[:, 1]
pred_062 = (prob_062 >= 0.45).astype(int)
print(f"ROC-AUC: {roc_auc_score(y_test, prob_062):.3f}")
print(classification_report(y_test, pred_062, target_names=["good product", "bad"], digits=3))

fig, axes = plt.subplots(1, 2, figsize=(10, 4))
ConfusionMatrixDisplay.from_predictions(y_test, pred_062, display_labels=["good product", "bad"], ax=axes[0], colorbar=False)
axes[0].set_title("Mixed queues under additional inspection rules")
axes[0].set_xlabel("Prediction Class")
axes[0].set_ylabel("Achievement Class")
axes[0].grid(False)
RocCurveDisplay.from_predictions(y_test, prob_062, ax=axes[1])
axes[1].set_title("Defective classificationROCcurve")
axes[1].set_xlabel("false positive rate")
axes[1].set_ylabel("True positivity rate")
axes[1].grid(True, alpha=0.3)
plt.tight_layout()
plt.show()
ROC-AUC: 0.811
              precision    recall  f1-score   support

          Good product 0.837 0.621 0.713 66
          Non-performing 0.554 0.795 0.653 39

    accuracy                          0.686       105
   macro avg      0.695     0.708     0.683       105
weighted avg      0.732     0.686     0.691       105



svg

Reading the results

The bottom right of the mixed queue indicates detected defects, while the bottom left indicates missed defects (horizontal axis is prediction, vertical axis is actual results). Lowering the threshold increases the reproduction rate of defects, but also increases additional inspections for good products. During implementation, Missed Fees × Number of false negatives + Additional Testing Costs × Number of Tests is compared by threshold and performance is monitored by product type.

No.063: Clustering — Segmenting Corporate Customers by Support Needs

Meaning in Practice

In industrial goods, when customer service is divided solely by sales scale, customers with high-frequency technical inquiries or those with large delivery times are overlooked. Clustering is a method to explore customer groups with similar purchasing and service characteristics.

Approach to Analysis and Modeling

K-means minimizes the sum of squared distances at each point and the center of the cluster of affiliation.

minC1,,CKk=1KxiCkxiμk2\min_{C_1,\ldots,C_K}\sum_{k=1}^K\sum_{x_i\in C_k}\lVert x_i-\mu_k\rVert^2

Standardize so that only large annual sales units do not dominate distance. There is no superiority or inferiority to cluster numbers themselves; you look at the summary table and add business names afterward.

Check with Python

n_customer = 75
base_segment = rng.choice(3, n_customer, p=[0.40, 0.35, 0.25])
annual_sales = np.exp(rng.normal(np.choose(base_segment, [4.0, 5.0, 4.5]), 0.35))
order_frequency = np.clip(rng.normal(np.choose(base_segment, [10, 28, 17]), 3.5), 3, None)
urgent_ratio = np.clip(rng.normal(np.choose(base_segment, [0.10, 0.18, 0.42]), 0.06), 0, 0.8)
support_hours = np.clip(rng.normal(np.choose(base_segment, [8, 16, 34]), 4), 1, None)
customers = pd.DataFrame({
    "customer_id": [f"C{i:03d}" for i in range(1, n_customer + 1)],
    "annual_sales_million_yen": annual_sales,
    "order_frequency": order_frequency,
    "urgent_order_ratio": urgent_ratio,
    "support_hours": support_hours,
})
cluster_features = customers.columns[1:]
X_scaled = StandardScaler().fit_transform(customers[cluster_features])
customers["cluster"] = KMeans(n_clusters=3, random_state=42, n_init=20).fit_predict(X_scaled)
profile_063 = customers.groupby("cluster")[cluster_features].mean()
profile_063["customers"] = customers.groupby("cluster").size()
display(profile_063.round(2))

fig, ax = plt.subplots(figsize=(7, 4))
for cluster, group in customers.groupby("cluster"):
    ax.scatter(group["annual_sales_million_yen"], group["support_hours"], label=f"Cluster {cluster}", alpha=0.75)
ax.set_title("Corporate Customers' Purchasing Scale and Technical Support Load")
ax.set_xlabel("Annual sales (million yen)")
ax.set_ylabel("Annual Technical Support Hours")
ax.grid(True, alpha=0.3)
ax.legend()
plt.tight_layout()
plt.show()
annual_sales_million_yen order_frequency urgent_order_ratio support_hours customers
cluster
0 93.73 17.02 0.38 34.58 25
1 66.72 9.13 0.10 8.12 28
2 152.65 28.49 0.19 15.65 22

svg

Reading the results

From the profile table, you can assign meanings such as “High Frequency/Large Volume,” “Emergency Response & High Support Load,” or “Standard Response.” Overlapping in scatter plots is natural, and clusters should not be treated as absolute customer classifications. It is practical for sales and service representatives to identify exception customers and track their movements every six months.

No.064: Dimensionality Reduction — Viewing Changes in Multivariable Sensors from Two Perspectives

Meaning in Practice

The equipment contains numerous signals such as temperature, vibration, current, and sound pressure. Since simply lining up individual graphs makes it difficult to capture simultaneous changes, principal component analysis (PCA) summarizes common variation into a few axes.

Approach to Analysis and Modeling

PCA determines the orthogonal direction in which the distribution of standardized data is maximized. The first principal component is z1=w1Txz_1=w_1^Tx, and w1w_1 is the unit vector that maximizes variance. The contribution rate is a guideline for the amount of information, but since there are local abnormalities not present in the main components, it does not mean discarding the original signal.

Check with Python

m = 260
load = rng.uniform(0.3, 1.0, m)
wear = np.linspace(0, 1, m) + rng.normal(0, 0.08, m)
sensors = pd.DataFrame({
    "temperature": 48 + 22 * load + 8 * wear + rng.normal(0, 1.5, m),
    "vibration": 1.0 + 1.2 * load + 1.5 * wear + rng.normal(0, 0.15, m),
    "current": 10 + 9 * load + rng.normal(0, 0.8, m),
    "sound": 62 + 8 * load + 5 * wear + rng.normal(0, 1.2, m),
    "oil_particles": 18 + 30 * wear + rng.normal(0, 3, m),
    "pressure": 4.0 + 2.8 * load - 0.5 * wear + rng.normal(0, 0.25, m),
})
pca_064 = PCA(n_components=2).fit(StandardScaler().fit_transform(sensors))
score_064 = pca_064.transform(StandardScaler().fit_transform(sensors))
loading_064 = pd.DataFrame(pca_064.components_.T, index=sensors.columns, columns=["PC1", "PC2"])
display(loading_064.round(3))
print("Cumulative contribution rate:", np.cumsum(pca_064.explained_variance_ratio_).round(3))

fig, axes = plt.subplots(1, 2, figsize=(10, 4))
axes[0].bar(["PC1", "PC2"], pca_064.explained_variance_ratio_)
axes[0].set_title("Contribution rate of principal components")
axes[0].set_xlabel("main component")
axes[0].set_ylabel("contribution rate")
axes[0].grid(True, axis="y", alpha=0.3)
sc = axes[1].scatter(score_064[:, 0], score_064[:, 1], c=wear, cmap="viridis", alpha=0.75)
axes[1].set_title("Main component score for equipment condition")
axes[1].set_xlabel("No.1main component")
axes[1].set_ylabel("No.2main component")
axes[1].grid(True, alpha=0.3)
fig.colorbar(sc, ax=axes[1], label="Abrasion (fictional)")
plt.tight_layout()
plt.show()
PC1 PC2
temperature 0.490 0.068
vibration 0.448 -0.327
current 0.389 0.431
sound 0.466 -0.101
oil_particles 0.323 -0.575
pressure 0.296 0.601
Cumulative contribution rate: [0.633 0.902]


svg

Reading the results

The contribution rate shows the extent of total variation across two axes, while the load scale shows which sensor is configured for each axis. If the score moves along the wear, it can be used as an auxiliary indicator for inspection triggers. However, the principal component code is arbitrary, and it is not fixed as “the higher the PC1, the more dangerous,” but interprets it in conjunction with the maintenance records.

No.065: Anomaly Detection — Monitoring Equipment with Few Failure Labels

Meaning in Practice

Major failures are usually rare, and it’s usually difficult to collect enough correct labels for supervised learning. Anomaly detection ranks observations that differ from the norm as candidates for inspection, supporting condition-based maintenance.

Approach to Analysis and Modeling

Isolation Forest isolates observations with random features and split points, and considers points with fewer fragments to be anomalies. contamination is the expected anomaly ratio, not the true failure rate itself. The score is not the probability of failure, but the relative deviation from the normal state.

Check with Python

monitor = sensors.copy()
monitor["timestamp"] = pd.date_range("2026-01-01", periods=len(monitor), freq="h")
anomaly_idx = np.array([68, 151, 218, 242])
monitor.loc[anomaly_idx, "vibration"] += [2.2, 2.5, 2.8, 2.4]
monitor.loc[anomaly_idx, "temperature"] += [12, 15, 11, 14]
features_065 = list(sensors.columns)
model_065 = IsolationForest(n_estimators=250, contamination=0.03, random_state=42)
monitor["anomaly_label"] = model_065.fit_predict(monitor[features_065])
monitor["anomaly_score"] = -model_065.score_samples(monitor[features_065])
display(monitor.nlargest(8, "anomaly_score")[["timestamp", "vibration", "temperature", "anomaly_score"]].round(3))

fig, ax = plt.subplots(figsize=(9, 4))
ax.plot(monitor["timestamp"], monitor["anomaly_score"], label="Abnormal Score")
flag = monitor["anomaly_label"] == -1
ax.scatter(monitor.loc[flag, "timestamp"], monitor.loc[flag, "anomaly_score"], color="red", label="inspection candidate", zorder=3)
ax.set_title("Abnormal Score Trends for Equipment Sensors")
ax.set_xlabel("time")
ax.set_ylabel("Anomaly score (the higher the abnormal score)")
ax.grid(True, alpha=0.3)
ax.legend()
plt.tight_layout()
plt.show()
timestamp vibration temperature anomaly_score
242 2026-01-11 02:00:00 5.392 78.802 0.639
68 2026-01-03 20:00:00 4.714 81.011 0.633
218 2026-01-10 02:00:00 5.692 79.421 0.629
151 2026-01-07 07:00:00 5.178 78.464 0.615
16 2026-01-01 16:00:00 1.670 52.597 0.604
10 2026-01-01 10:00:00 1.299 53.733 0.589
1 2026-01-01 01:00:00 2.039 67.991 0.572
230 2026-01-10 14:00:00 3.797 74.965 0.567

svg

Reading the results

A red mark is not a stop order, but an inspection candidate where maintenance staff check the waveform, machining conditions, and the work before the work. If the same time period appears consecutively in the top table, it is grouped as a single event. It is necessary to accumulate false alarm records and actual failure histories, and update thresholds by equipment and operating mode.

No.066: Time Series Forecasting — Forecasting Replacement Parts Demand Including Seasonality

Meaning in Practice

Forecasting the demand for spare parts is fundamental to production slots, material orders, and safety stock. In random partitioning, future observations are mixed into learning, so evaluations follow the order from past to future.

Approach to Analysis and Modeling

Here, trends and weekly/monthly cycles are represented by Fourier characteristics, with linear regression.

yt=β0+β1t+k{aksin(2πt/Pk)+bkcos(2πt/Pk)}+εty_t=\beta_0+\beta_1t+\sum_k\{a_k\sin(2\pi t/P_k)+b_k\cos(2\pi t/P_k)\}+\varepsilon_t

The last 30 days are used as the test period, followed by 30 days to forecast the future. In practice, explanatory variables known at the time of forecasting, such as promotions, holidays, and large projects, are also added.

Check with Python

days = 240
t = np.arange(days)
demand = (82 + 0.09 * t + 13 * np.sin(2 * np.pi * t / 7)
          + 8 * np.sin(2 * np.pi * t / 30) + rng.normal(0, 6, days))
demand = np.clip(np.rint(demand), 0, None)
demand_df = pd.DataFrame({"date": pd.date_range("2025-09-01", periods=days, freq="D"), "demand": demand})

def calendar_features(index):
    index = np.asarray(index)
    return np.column_stack([
        index,
        np.sin(2 * np.pi * index / 7), np.cos(2 * np.pi * index / 7),
        np.sin(2 * np.pi * index / 30), np.cos(2 * np.pi * index / 30),
    ])

train_end = days - 30
model_066 = LinearRegression().fit(calendar_features(t[:train_end]), demand[:train_end])
test_pred = model_066.predict(calendar_features(t[train_end:]))
future_t = np.arange(days, days + 30)
future_pred = model_066.predict(calendar_features(future_t))
print(f"most recent30Day ChronologyMAE: {mean_absolute_error(demand[train_end:], test_pred):.2f} units/days")

fig, ax = plt.subplots(figsize=(10, 4))
ax.plot(demand_df["date"].iloc[-90:], demand[-90:], label="Achievements")
ax.plot(demand_df["date"].iloc[train_end:], test_pred, label="Test Prediction", linestyle="--")
future_date = pd.date_range(demand_df["date"].iloc[-1] + pd.Timedelta(days=1), periods=30, freq="D")
ax.plot(future_date, future_pred, label="future30Forecast for the day", color="red")
ax.set_title("Daily Demand Forecast for Replacement Parts")
ax.set_xlabel("Date")
ax.set_ylabel("Needs (number/Day)")
ax.grid(True, alpha=0.3)
ax.legend()
plt.tight_layout()
plt.show()
Time-series MAE over the past 30 days: 4.30 units/day


svg

Reading the results

Time Series MAE is the error measured over the most recent 30 days as an unknown period. The future line is an average forecast and does not represent upside risk. For procurement and inventory, we create prediction intervals from the residual distribution and use the upper quantile according to out-of-stock costs and inventory costs. In case structural changes occur, we save the “forecast timeline, forecast value, and actual value” every month.

No.067: Causal Inference — Estimating the Pure Time-Saving Effect of the New Jig

Meaning in Practice

When improvement jigs are prioritized for difficult projects, the processing time of the introduced group appears to be high. The simple average difference before and after implementation, or between implementation and non-implementation, can be mistaken for differences in project composition (confusion) with the effectiveness of the initiative.

Approach to Analysis and Modeling

Estimate the propensity score e(x)=P(T=1X=x)e(x)=P(T=1\mid X=x) and create a comparable pseudopopulation using inverse probability weighting (IPW).

ATE^=TiYi/eiTi/ei(1Ti)Yi/(1ei)(1Ti)/(1ei)\widehat{ATE}=\frac{\sum T_iY_i/e_i}{\sum T_i/e_i} -\frac{\sum(1-T_i)Y_i/(1-e_i)}{\sum(1-T_i)/(1-e_i)}

The necessary assumptions are: if conditioned by measured confounding factors, the allocation is independent, both groups exist, and treatment definitions are consistent. Unobserved proficiency cannot be corrected.

Check with Python

n_067 = 700
complexity_067 = rng.uniform(1, 10, n_067)
lot_067 = rng.integers(30, 150, n_067)
assign_logit = -2.0 + 0.40 * complexity_067 + 0.004 * lot_067
prop_true = 1 / (1 + np.exp(-assign_logit))
treatment = rng.binomial(1, prop_true)
true_effect = -11.0
outcome = 45 + 8.0 * complexity_067 + 0.30 * lot_067 + true_effect * treatment + rng.normal(0, 8, n_067)
causal = pd.DataFrame({"complexity": complexity_067, "lot_size": lot_067, "new_jig": treatment, "cycle_time": outcome})

naive = causal.loc[causal.new_jig == 1, "cycle_time"].mean() - causal.loc[causal.new_jig == 0, "cycle_time"].mean()
ps_model = make_pipeline(StandardScaler(), LogisticRegression()).fit(causal[["complexity", "lot_size"]], treatment)
ps = np.clip(ps_model.predict_proba(causal[["complexity", "lot_size"]])[:, 1], 0.05, 0.95)
treated_mean = np.sum(treatment * outcome / ps) / np.sum(treatment / ps)
control_mean = np.sum((1-treatment) * outcome / (1-ps)) / np.sum((1-treatment) / (1-ps))
ipw_ate = treated_mean - control_mean
result_067 = pd.DataFrame({
    "Estimation method": ["simple mean deviation", "IPW", "True values for data generation"],
    "Time Reduction Effect (minutes)": [naive, ipw_ate, true_effect],
})
display(result_067.round(2))

fig, ax = plt.subplots(figsize=(7, 4))
ax.hist(ps[treatment == 0], bins=15, alpha=0.6, label="not introduced")
ax.hist(ps[treatment == 1], bins=15, alpha=0.6, label="Introduction")
ax.set_title("Distribution of Trend Scores for New Jig Introduction")
ax.set_xlabel("Probability of Introduction (Trend Score)")
ax.set_ylabel("Number of Cases")
ax.grid(True, alpha=0.3)
ax.legend()
plt.tight_layout()
plt.show()
Estimation method Time Reduction Effect (minutes)
0 simple mean deviation 8.69
1 IPW -12.05
2 True values for data generation -11.00

svg

Reading the results

Since the more difficult the case, the more it is adopted, so simple comparisons tend to underestimate the true shortening effect, and IPW brings you closer to the true value. Areas with little overlap in the trend score distribution cannot be compared from the data. If possible, we design phased implementations or randomized comparisons, and simultaneously evaluate whether there are any adverse impacts on safety or quality.

No.068: Bayesian Optimization — Exploring Machining Conditions with Minimal Prototyping

Meaning in Practice

Searching for conditions such as cutting speed incurs material, equipment, time, and inspection costs for each prototype. Bayesian optimization estimates performance under unknown conditions from a small number of evaluation results, then tests the next “good” and “uncertain” conditions.

Approach to Analysis and Modeling

In the Gaussian process, the mean μ(x)\mu(x) and standard deviation σ(x)\sigma(x) of the objective function are described, and candidates are selected to maximize Expected Improvement (EI).

EI(x)=(fbestμ(x))Φ(Z)+σ(x)ϕ(Z),Z=fbestμ(x)σ(x)EI(x)=(f_{best}-\mu(x))\Phi(Z)+\sigma(x)\phi(Z),\quad Z=\frac{f_{best}-\mu(x)}{\sigma(x)}

Here, losses are minimized by combining surface roughness and cycle time. In practice, tool lifespan, quality standards, and equipment limits are clearly indicated as constraints.

Check with Python

def machining_loss(speed, feed):
    base = 1.2 + ((speed - 185) / 38) ** 2 + ((feed - 0.20) / 0.055) ** 2
    interaction = 0.35 * np.sin(speed / 18) * np.cos(feed * 30)
    return base + interaction

grid_speed = np.linspace(120, 240, 45)
grid_feed = np.linspace(0.10, 0.30, 45)
S, F = np.meshgrid(grid_speed, grid_feed)
grid_X = np.column_stack([S.ravel(), F.ravel()])
scale = np.array([120.0, 0.20])
X_obs = np.array([[130, 0.12], [155, 0.26], [205, 0.14], [230, 0.28]], dtype=float)
y_obs = np.array([machining_loss(*x) for x in X_obs]) + rng.normal(0, 0.04, len(X_obs))

for _ in range(10):
    kernel = ConstantKernel(1.0) * Matern(length_scale=[0.3, 0.3], nu=2.5) + WhiteKernel(0.002)
    gp = GaussianProcessRegressor(kernel=kernel, normalize_y=True, random_state=42, n_restarts_optimizer=1)
    gp.fit(X_obs / scale, y_obs)
    mu, sigma = gp.predict(grid_X / scale, return_std=True)
    improvement = y_obs.min() - mu
    z = improvement / np.maximum(sigma, 1e-9)
    ei = improvement * norm.cdf(z) + sigma * norm.pdf(z)
    next_x = grid_X[np.argmax(ei)]
    X_obs = np.vstack([X_obs, next_x])
    y_obs = np.append(y_obs, machining_loss(*next_x) + rng.normal(0, 0.04))

best_idx = np.argmin(y_obs)
best_068 = pd.DataFrame({"cutting speed": [X_obs[best_idx, 0]], "Delivery volume": [X_obs[best_idx, 1]], "Observation loss": [y_obs[best_idx]]})
display(best_068.round(3))

fig, ax = plt.subplots(figsize=(8, 5))
contour = ax.contourf(S, F, machining_loss(S, F), levels=18, cmap="viridis")
ax.scatter(X_obs[:4, 0], X_obs[:4, 1], color="white", edgecolor="black", label="initial condition")
ax.scatter(X_obs[4:, 0], X_obs[4:, 1], color="red", s=28, label="explore step by step")
ax.scatter(X_obs[best_idx, 0], X_obs[best_idx, 1], marker="*", s=180, color="gold", edgecolor="black", label="Best observation")
ax.set_title("Searching machining conditions through Bayesian optimization")
ax.set_xlabel("Cutting speed (m/min)")
ax.set_ylabel("Feed Amount (mm/rev)")
ax.grid(True, alpha=0.25)
ax.legend()
fig.colorbar(contour, ax=ax, label="Processing loss (less, better)")
plt.tight_layout()
plt.show()
cutting speed Delivery volume Observation loss
0 188.182 0.2 0.882

svg

Reading the results

Red dots were successively selected as prototype conditions, and the star mark was the best observational condition. While the search points are gathering near the best possible points, you can also see that uncertain areas are being tested. It is important not to simply apply these results as standard conditions, but to verify replication tests, quality standards, tool wear, and safety limits, and not extrapolate beyond the scope of exploration.

No.069: Reinforcement Learning — Learning Conservation Policies According to Deterioration Conditions

Meaning in Practice

If preventive maintenance is performed too early, parts cannot be fully utilized; if delayed too long, failures and losses increase. Reinforcement learning repeatedly evaluates the current state of deterioration and behavior, as well as subsequent conditions and costs, and learns strategies that consider long-term costs.

Approach to Analysis and Modeling

Status is rated as healthy from 0 to 4, and actions are set as ‘Continue Driving’ and ‘Maintain.’ Q-learning updates behavioral value using the following formula.

Q(s,a)Q(s,a)+α{r+γmaxaQ(s,a)Q(s,a)}Q(s,a)\leftarrow Q(s,a)+\alpha\{r+\gamma\max_{a'}Q(s',a')-Q(s,a)\}

Since compensation design determines the conclusion, maintenance costs, breakdown costs, and production profits must be defined in business figures. Instead of direct exploration at the actual facility, we first verify it using simulators and historical data.

Check with Python

n_states, n_actions = 5, 2
Q = np.zeros((n_states, n_actions))
alpha, gamma, epsilon = 0.12, 0.96, 0.15
episode_returns = []

def step_069(state, action, random_gen):
    if action == 1:  # preserve
        return 0, -18.0
    if state == 4:  # Continued in a malfunctioning state
        return 0, -85.0
    deteriorate_prob = 0.18 + 0.12 * state
    next_state = min(4, state + int(random_gen.random() < deteriorate_prob))
    reward = 8.0 - 1.5 * state
    if next_state == 4:
        reward -= 55.0
    return next_state, reward

for episode in range(5000):
    state, total = 0, 0.0
    for _ in range(80):
        if rng.random() < epsilon:
            action = rng.integers(n_actions)
        else:
            action = int(np.argmax(Q[state]))
        next_state, reward = step_069(state, action, rng)
        Q[state, action] += alpha * (reward + gamma * Q[next_state].max() - Q[state, action])
        state, total = next_state, total + reward
    episode_returns.append(total)

policy = np.argmax(Q, axis=1)
policy_069 = pd.DataFrame({
    "state of deterioration": np.arange(n_states),
    "ContinuationQvalue": Q[:, 0], "preserveQvalue": Q[:, 1],
    "Recommended Actions": np.where(policy == 0, "Continued operation", "preserve"),
})
display(policy_069.round(2))

rolling = pd.Series(episode_returns).rolling(200).mean()
fig, ax = plt.subplots(figsize=(8, 4))
ax.plot(rolling, color="tab:blue")
ax.set_title("Average cumulative reward for conservation policy learning")
ax.set_xlabel("Learning Episodes")
ax.set_ylabel("most recent200Average cumulative rewards per session")
ax.grid(True, alpha=0.3)
plt.tight_layout()
plt.show()
state of deterioration ContinuationQvalue preserveQvalue Recommended Actions
0 0 125.80 105.74 Continued operation
1 1 112.67 104.70 Continued operation
2 2 106.98 105.37 Continued operation
3 3 64.38 105.16 preserve
4 4 37.63 105.25 preserve

svg

Reading the results

The Q value table shows the long-term value and recommended behavior for each condition, and switches to conservation as deterioration progresses. The transition point depends on the set failure cost, maintenance cost, and transition probability. On-site deployment starts with decision support by offline comparison of existing rules and simulation measures, presenting only actions permitted by safety constraints.

No.070: Graph Machine Learning — Aggregating Nearby Risks in Supplier Networks

Meaning in Practice

Supply suspension risks ripple not only through each company’s finances and quality, but also through dependence on the same logistics hubs and secondary suppliers. The basic aspect of graph machine learning is to add features to ‘who is connected to,’ which is lost in table formats.

Approach to Analysis and Modeling

Aggregate the average features of adjacent nodes into feature xvx_v of each node vv.

hv=[xv    1N(v)uN(v)xu]h_v=\left[x_v\;\middle\|\;\frac{1}{|N(v)|}\sum_{u\in N(v)}x_u\right]

This is a simplified step of message passing in graph neural networks. Risk is estimated using logistic regression and compared to cases where individual characteristics are used alone. It is necessary to distinguish between no transactions and data loss.

Check with Python

n_nodes = 90
rng_070 = np.random.default_rng(42)
G = nx.stochastic_block_model([30, 30, 30], [[0.20, 0.02, 0.01], [0.02, 0.18, 0.03], [0.01, 0.03, 0.22]], seed=42)
community = np.repeat(np.arange(3), 30)
quality_issue = np.clip(rng_070.beta(2, 8, n_nodes) + 0.12 * (community == 2), 0, 1)
delivery_delay = np.clip(rng_070.beta(2, 7, n_nodes) + 0.12 * (community == 1), 0, 1)
financial_stress = np.clip(rng_070.beta(2, 6, n_nodes), 0, 1)
base_risk = 1.4 * quality_issue + 1.2 * delivery_delay + 1.0 * financial_stress
neighbor_base = np.array([np.mean([base_risk[u] for u in G.neighbors(v)]) for v in G.nodes])
risk_prob = 1 / (1 + np.exp(-(-2.0 + 0.3 * base_risk + 2.2 * neighbor_base)))
risk_label = rng_070.binomial(1, risk_prob)

own_X = np.column_stack([quality_issue, delivery_delay, financial_stress])
neighbor_X = np.array([own_X[list(G.neighbors(v))].mean(axis=0) for v in G.nodes])
graph_X = np.column_stack([own_X, neighbor_X])
train_idx, test_idx = train_test_split(np.arange(n_nodes), test_size=0.35, random_state=42, stratify=risk_label)
own_model = make_pipeline(StandardScaler(), LogisticRegression(class_weight="balanced", random_state=42)).fit(own_X[train_idx], risk_label[train_idx])
graph_model = make_pipeline(StandardScaler(), LogisticRegression(class_weight="balanced", random_state=42)).fit(graph_X[train_idx], risk_label[train_idx])
own_prob = own_model.predict_proba(own_X[test_idx])[:, 1]
graph_prob = graph_model.predict_proba(graph_X[test_idx])[:, 1]
comparison_070 = pd.DataFrame({
    "Model": ["Individual Company Features Only", "Individual company + Neighborhood aggregation"],
    "TestROC-AUC": [roc_auc_score(risk_label[test_idx], own_prob), roc_auc_score(risk_label[test_idx], graph_prob)],
})
display(comparison_070.round(3))

all_graph_prob = graph_model.predict_proba(graph_X)[:, 1]
pos = nx.spring_layout(G, seed=42)
fig, ax = plt.subplots(figsize=(8, 6))
nodes = nx.draw_networkx_nodes(G, pos, node_color=all_graph_prob, cmap="Reds", node_size=110, ax=ax)
nx.draw_networkx_edges(G, pos, alpha=0.22, ax=ax)
ax.set_title("Supplier trading networks and estimated supply risks")
ax.set_xlabel("Network Layout X(For display purposes)")
ax.set_ylabel("Network Layout Y(For display purposes)")
ax.grid(True, alpha=0.15)
fig.colorbar(nodes, ax=ax, label="Estimated risk probability")
plt.tight_layout()
plt.show()
Model TestROC-AUC
0 Individual Company Features Only 0.496
1 Individual company + Neighborhood aggregation 0.609

svg

Reading the results

Models with neighborhood aggregation do not always win, and differences in test AUC serve as evidence to verify the added value of network information. In areas with concentrated red nodes, we check not only individual company audits but also common logistics, secondary business partners, and alternative procurement. Orders are not stopped based solely on predicted probabilities; instead, explainable risk factors are combined with confirmation by procurement staff.

Practical Implications Seen Through Target Exercise

  1. Decisions are made before the targets of prediction: For workload forecasting, we define delivery responses; for defect classification, we define additional inspections, users, timing, and options.
  2. Tailoring splitting methods and evaluation metrics to business operations: Time series tests the future, while imbalance classification looks at recall and false detection costs. Average accuracy alone is not enough.
  3. Distinguishing Correlation, Prediction, and Causality: Even if prediction accuracy is high, the effects of changing conditions are unknown. Improvement effects require experimental design or contamination adjustment.
  4. Dealing with relationships and time, not points: Time series, reinforcement learning, and graphs introduce dependencies into the model that are difficult to represent in a standalone, single-row table.
  5. Output that allows human intervention: Set thresholds, rationales, exception handling, and final permissions for probability, anomaly scores, and recommended measures.

What is necessary for practical implementation

1. Business Definition and Baseline

Before introducing the model, record current rules, decision frequency, responsible personnel, and misjudgment costs. In addition to advanced models, we measure added value by comparing with “category averages,” “previous week’s values,” and “current inspection standards.”

2. Data Quality and Timing Alignment

Equipment, product types, lots, and supplier IDs are unified, and the reason for the loss and sensor calibration history are recorded. Check whether features are available at the time of prediction and whether any later adjusted actual values are mixed in.

3. Verification, Safety, and Responsibility Separation

During shadow operations, we compare recommendations with actual judgments and check performance by type, equipment, and period. Safety and quality decisions prioritize constraint rules, clearly specifying automation scope, approvers, and stop/manual return procedures.

4. Continuous Monitoring

Monitor input distribution, prediction accuracy, intervention rate, and business KPIs to detect discrepancies between training data and actual data. Not only the retraining cycle, but also the conditions under which the model is left or stopped are determined.

Conclusion

Topics No.061 to No.070 covered machining time and defect prediction, structural understanding of customer and equipment conditions, temporal monitoring of demand and anomalies, causal estimation of improvement effects, sequential decision-making for prototyping and maintenance, and risk assessment of transaction networks.

The key to establishing machine learning in manufacturing is not the complex model itself, but the Timely forecasted data, cost-based evaluation, on-site constraints, final human decisions, and performance feedback. First, a baseline is established in the limited process, and while measuring whether the quality of decision-making has improved, the scope of application is expanded.

Consultations for Corporations

At Surikoubo, we offer consultations ranging from selecting machine learning and statistical analysis themes in manufacturing, to PoC, data infrastructure, business implementation, continuous monitoring of accuracy and effectiveness, and support for in-house production.

  • Design and Verification of Quality Forecasting, Equipment Anomaly Detection, and Demand Forecasting
  • Experimental design and causal effect evaluation of improvement measures
  • Processing Condition Exploration, Conservation Measures, and Supply Chain Risk Analysis
  • Design of on-site operational KPIs, explanatory screens, and approval workflows

📩 Contact Us: surikobo.co.jp/contact Please feel free to consult us first.