一、交通分类的本质:从“看表”到“读心”

传统方法认为“17:00-19:00 就是高峰”,但现实远比这复杂:

  • 工作日 vs 周末:周五晚高峰可能提前。
  • 天气影响:暴雨天通勤时间延长30%。
  • 突发事件:一场演唱会结束瞬间引发区域拥堵。
  • 长期趋势:新地铁线开通后,旧主干道车流下降。

因此,真正的交通分类必须是多维、动态、可学习的。

核心挑战:

  1. 数据从哪来? —— 模拟真实交通传感器数据流。
  2. 特征怎么选? —— 如何从原始数据中提取有效信息?
  3. 模型如何训练? —— 选择最适合时序分类的算法。
  4. 如何实时预测? —— 在毫秒级响应用户请求。

我们将用 Java 逐一攻克。


二、数据模型:定义交通状态

首先,定义交通数据的基本单元。

import java.time.LocalDateTime;
import java.time.temporal.ChronoUnit;

/**
 * 表示某一时刻某一路段的交通状态快照
 * 这是所有分析的基础数据单元
 */
public class TrafficSnapshot {
    
    /**
     * 时间戳,精确到分钟
     * 是最重要的特征之一
     */
    private LocalDateTime timestamp;
    
    /**
     * 路段ID,标识具体道路
     * 可扩展为GPS坐标范围
     */
    private String roadSegmentId;
    
    /**
     * 平均车速 (km/h)
     * 核心指标:速度越低,拥堵越严重
     */
    private double averageSpeed;
    
    /**
     * 车流量 (辆/小时)
     * 高流量不一定拥堵,需结合速度判断
     */
    private int vehicleCount;
    
    /**
     * 占有率 (%)
     * 雷达或摄像头检测到车辆占用道路的时间比例
     * >70% 通常表示拥堵
     */
    private double occupancyRate;
    
    /**
     * 天气状况
     * 晴、雨、雪、雾等
     * 显著影响交通
     */
    private WeatherCondition weather;
    
    /**
     * 是否为节假日
     * 国家法定假日或地方特殊节日
     */
    private boolean isHoliday;
    
    /**
     * 事件标志
     * 如事故、施工、大型活动等
     */
    private TrafficEvent eventFlag;
    
    // 构造函数
    public TrafficSnapshot(LocalDateTime timestamp, 
                          String roadSegmentId, 
                          double averageSpeed, 
                          int vehicleCount, 
                          double occupancyRate,
                          WeatherCondition weather,
                          boolean isHoliday,
                          TrafficEvent eventFlag) {
        this.timestamp = timestamp;
        this.roadSegmentId = roadSegmentId;
        this.averageSpeed = averageSpeed;
        this.vehicleCount = vehicleCount;
        this.occupancyRate = occupancyRate;
        this.weather = weather;
        this.isHoliday = isHoliday;
        this.eventFlag = eventFlag;
    }
    
    // Getters and Setters ...
    public LocalDateTime getTimestamp() { return timestamp; }
    public void setTimestamp(LocalDateTime timestamp) { this.timestamp = timestamp; }
    
    public String getRoadSegmentId() { return roadSegmentId; }
    public void setRoadSegmentId(String roadSegmentId) { this.roadSegmentId = roadSegmentId; }
    
    public double getAverageSpeed() { return averageSpeed; }
    public void setAverageSpeed(double averageSpeed) { this.averageSpeed = averageSpeed; }
    
    public int getVehicleCount() { return vehicleCount; }
    public void setVehicleCount(int vehicleCount) { this.vehicleCount = vehicleCount; }
    
    public double getOccupancyRate() { return occupancyRate; }
    public void setOccupancyRate(double occupancyRate) { this.occupancyRate = occupancyRate; }
    
    public WeatherCondition getWeather() { return weather; }
    public void setWeather(WeatherCondition weather) { this.weather = weather; }
    
    public boolean isHoliday() { return isHoliday; }
    public void setHoliday(boolean holiday) { isHoliday = holiday; }
    
    public TrafficEvent getEventFlag() { return eventFlag; }
    public void setEventFlag(TrafficEvent eventFlag) { this.eventFlag = eventFlag; }
    
    /**
     * 判断当前是否为高峰时段(人工标注,用于训练)
     * 实际系统中可通过历史平均速度对比生成
     */
    public boolean isPeakHour() {
        // 简化规则:速度 < 20 km/h 且 占有率 > 65% 视为高峰
        return averageSpeed < 20.0 && occupancyRate > 0.65;
    }
    
    @Override
    public String toString() {
        return String.format("TrafficSnapshot{time=%s, road=%s, speed=%.1f, count=%d, occ=%.1f%%, weather=%s, holiday=%b, event=%s}",
                timestamp, roadSegmentId, averageSpeed, vehicleCount, occupancyRate*100, weather, isHoliday, eventFlag);
    }
}

/**
 * 天气枚举
 */
enum WeatherCondition {
    CLEAR, RAIN, SNOW, FOG, CLOUDY
}

/**
 * 交通事件枚举
 */
enum TrafficEvent {
    NONE, ACCIDENT, CONSTRUCTION, EVENT, ROAD_CLOSED
}

三、特征工程:从原始数据到“诊断依据”

原始数据不能直接喂给模型,必须进行特征提取

import java.util.*;
import java.util.stream.Collectors;

/**
 * 特征工程处理器
 * 将原始 TrafficSnapshot 转换为机器学习可用的数值特征向量
 */
public class FeatureExtractor {
    
    /**
     * 提取单个样本的特征向量
     * 返回一个双精度数组,供模型使用
     */
    public double[] extractFeatures(TrafficSnapshot snapshot, List<TrafficSnapshot> historicalData) {
        List<Double> features = new ArrayList<>();
        
        // 1. 时间特征 (Time-based Features)
        features.addAll(extractTimeFeatures(snapshot.getTimestamp()));
        
        // 2. 当前状态特征 (Current State)
        features.add(snapshot.getAverageSpeed());
        features.add(snapshot.getVehicleCount());
        features.add(snapshot.getOccupancyRate());
        
        // 3. 天气独热编码 (One-Hot Encoding for Weather)
        features.addAll(encodeWeather(snapshot.getWeather()));
        
        // 4. 事件标志 (Event Flags)
        features.add(snapshot.isHoliday() ? 1.0 : 0.0);
        features.add(eventToNumeric(snapshot.getEventFlag()));
        
        // 5. 历史对比特征 (Historical Comparison)
        if (historicalData != null && !historicalData.isEmpty()) {
            features.addAll(extractHistoricalFeatures(snapshot, historicalData));
        }
        
        // 6. 流动性指标 (Derived Metrics)
        features.add(calculateCongestionIndex(snapshot));
        features.add(calculateFlowEfficiency(snapshot));
        
        // 转换为数组
        return features.stream().mapToDouble(Double::doubleValue).toArray();
    }
    
    /**
     * 提取时间相关特征
     * 将时间转换为周期性数值,便于模型理解
     */
    private List<Double> extractTimeFeatures(LocalDateTime time) {
        List<Double> timeFeatures = new ArrayList<>();
        
        // 小时 (0-23) -> 正弦/余弦编码,解决 23 和 0 的跳跃问题
        int hour = time.getHour();
        timeFeatures.add(Math.sin(2 * Math.PI * hour / 24.0));
        timeFeatures.add(Math.cos(2 * Math.PI * hour / 24.0));
        
        // 星期几 (1=周一, 7=周日)
        int dayOfWeek = time.getDayOfWeek().getValue();
        timeFeatures.add((double) dayOfWeek);
        
        // 是否工作日
        timeFeatures.add(dayOfWeek <= 5 ? 1.0 : 0.0);
        
        // 月份 (季节性)
        int month = time.getMonthValue();
        timeFeatures.add((double) month);
        
        // 季节 (1=冬, 2=春, 3=夏, 4=秋)
        int season = (month % 12) / 3 + 1;
        timeFeatures.add((double) season);
        
        return timeFeatures;
    }
    
    /**
     * 天气独热编码
     * [晴, 雨, 雪, 雾, 多云]
     */
    private List<Double> encodeWeather(WeatherCondition weather) {
        List<Double> encoding = Arrays.asList(0.0, 0.0, 0.0, 0.0, 0.0);
        switch (weather) {
            case CLEAR: encoding.set(0, 1.0); break;
            case RAIN:  encoding.set(1, 1.0); break;
            case SNOW:  encoding.set(2, 1.0); break;
            case FOG:   encoding.set(3, 1.0); break;
            case CLOUDY:encoding.set(4, 1.0); break;
        }
        return encoding;
    }
    
    /**
     * 事件转数值
     */
    private double eventToNumeric(TrafficEvent event) {
        switch (event) {
            case NONE:       return 0.0;
            case ACCIDENT:   return 3.0;
            case CONSTRUCTION:return 2.0;
            case EVENT:      return 2.5;
            case ROAD_CLOSED:return 4.0;
            default:         return 0.0;
        }
    }
    
    /**
     * 提取历史对比特征
     * 让模型理解“当前情况相比平时如何”
     */
    private List<Double> extractHistoricalFeatures(TrafficSnapshot current, List<TrafficSnapshot> history) {
        List<Double> histFeatures = new ArrayList<>();
        
        // 过滤出同一路段、同一星期几的历史数据
        List<TrafficSnapshot> relevantHistory = history.stream()
                .filter(s -> s.getRoadSegmentId().equals(current.getRoadSegmentId()))
                .filter(s -> s.getTimestamp().getDayOfWeek() == current.getTimestamp().getDayOfWeek())
                .collect(Collectors.toList());
        
        if (relevantHistory.isEmpty()) {
            // 无历史数据,返回默认值
            histFeatures.add(0.0); // 速度偏差
            histFeatures.add(0.0); // 流量偏差
            histFeatures.add(0.0); // 拥堵指数偏差
            return histFeatures;
        }
        
        // 计算历史平均值
        double avgSpeedHist = relevantHistory.stream().mapToDouble(TrafficSnapshot::getAverageSpeed).average().orElse(30.0);
        double avgCountHist = relevantHistory.stream().mapToDouble(s -> s.getVehicleCount()).average().orElse(100.0);
        double avgOccHist = relevantHistory.stream().mapToDouble(s -> s.getOccupancyRate()).average().orElse(0.5);
        
        // 计算当前与历史的偏差(标准化)
        double speedDeviation = (current.getAverageSpeed() - avgSpeedHist) / (avgSpeedHist + 1.0);
        double countDeviation = (current.getVehicleCount() - avgCountHist) / (avgCountHist + 1.0);
        double occDeviation = (current.getOccupancyRate() - avgOccHist) / (avgOccHist + 0.01);
        
        histFeatures.add(speedDeviation);
        histFeatures.add(countDeviation);
        histFeatures.add(occDeviation);
        
        return histFeatures;
    }
    
    /**
     * 拥堵指数 (0-1)
     * 综合速度和占有率
     */
    private double calculateCongestionIndex(TrafficSnapshot snapshot) {
        // 速度权重 0.6, 占有率权重 0.4
        double speedScore = 1.0 - Math.min(snapshot.getAverageSpeed() / 60.0, 1.0); // 60km/h 为畅通
        double occScore = snapshot.getOccupancyRate();
        return 0.6 * speedScore + 0.4 * occScore;
    }
    
    /**
     * 流动效率 (0-1)
     * 高效 = 高流量 + 高速度
     */
    private double calculateFlowEfficiency(TrafficSnapshot snapshot) {
        double normalizedSpeed = snapshot.getAverageSpeed() / 80.0; // 假设限速80
        double normalizedFlow = snapshot.getVehicleCount() / 500.0; // 假设最大流量500
        return Math.min(normalizedSpeed * normalizedFlow, 1.0);
    }
}

四、机器学习模型:三大算法深度实现

1. 逻辑回归(Logistic Regression)—— 基准模型

/**
 * 逻辑回归分类器
 * 线性模型,解释性强,适合作为基准
 */
public class LogisticRegressionClassifier {
    
    private double[] weights; // 模型权重
    private double bias;      // 偏置项
    private double learningRate = 0.01;
    private int maxIterations = 1000;
    
    public LogisticRegressionClassifier() {}
    
    /**
     * Sigmoid 激活函数
     * 将线性输出映射到 (0,1) 概率
     */
    private double sigmoid(double z) {
        // 防止溢出
        if (z > 10.0) return 1.0;
        if (z < -10.0) return 0.0;
        return 1.0 / (1.0 + Math.exp(-z));
    }
    
    /**
     * 训练模型
     */
    public void train(List<double[]> features, List<Boolean> labels) {
        if (features.isEmpty() || features.size() != labels.size()) {
            throw new IllegalArgumentException("Features and labels size mismatch");
        }
        
        int nSamples = features.size();
        int nFeatures = features.get(0).length;
        
        // 初始化权重
        weights = new double[nFeatures];
        bias = 0.0;
        
        // 随机初始化权重(小范围)
        Random rand = new Random(42);
        for (int i = 0; i < nFeatures; i++) {
            weights[i] = (rand.nextDouble() - 0.5) * 0.1;
        }
        
        // 梯度下降
        for (int iter = 0; iter < maxIterations; iter++) {
            double dwSum = 0.0;
            double dbSum = 0.0;
            
            for (int i = 0; i < nSamples; i++) {
                double[] x = features.get(i);
                boolean y = labels.get(i);
                
                // 前向传播
                double z = bias;
                for (int j = 0; j < nFeatures; j++) {
                    z += weights[j] * x[j];
                }
                double a = sigmoid(z);
                
                // 计算损失梯度
                double dz = a - (y ? 1.0 : 0.0);
                dbSum += dz;
                for (int j = 0; j < nFeatures; j++) {
                    weights[j] -= learningRate * dz * x[j]; // 在这里更新更高效
                }
            }
            
            // 更新偏置
            bias -= learningRate * dbSum / nSamples;
            
            // 可选:打印损失
            if (iter % 100 == 0) {
                double loss = computeLoss(features, labels);
                System.out.printf("[LR] Iteration %d, Loss: %.6f%n", iter, loss);
            }
        }
    }
    
    /**
     * 计算对数损失
     */
    private double computeLoss(List<double[]> features, List<Boolean> labels) {
        double loss = 0.0;
        for (int i = 0; i < features.size(); i++) {
            double[] x = features.get(i);
            boolean y = labels.get(i);
            double z = bias;
            for (int j = 0; j < weights.length; j++) {
                z += weights[j] * x[j];
            }
            double a = sigmoid(z);
            // 防止 log(0)
            a = Math.max(1e-15, Math.min(1 - 1e-15, a));
            loss += y ? -Math.log(a) : -Math.log(1 - a);
        }
        return loss / features.size();
    }
    
    /**
     * 预测
     * @return 概率值 [0,1]
     */
    public double predictProbability(double[] features) {
        if (weights == null) throw new IllegalStateException("Model not trained");
        
        double z = bias;
        for (int i = 0; i < weights.length; i++) {
            z += weights[i] * features[i];
        }
        return sigmoid(z);
    }
    
    /**
     * 分类
     * @param threshold 决策阈值,默认 0.5
     * @return true=高峰, false=低峰
     */
    public boolean predict(double[] features, double threshold) {
        return predictProbability(features) >= threshold;
    }
}

2. 随机森林(Random Forest)—— 非线性王者

import java.util.*;

/**
 * 随机森林分类器
 * 集成多个决策树,抗过拟合,处理非线性关系
 */
public class RandomForestClassifier {
    
    private List<DecisionTree> trees;
    private int nTrees = 100;
    private int maxDepth = 10;
    private int minSamplesSplit = 2;
    private Random random;
    
    public RandomForestClassifier() {
        this.random = new Random(42);
    }
    
    /**
     * 训练随机森林
     */
    public void train(List<double[]> features, List<Boolean> labels) {
        trees = new ArrayList<>();
        int nSamples = features.size();
        int nFeatures = features.get(0).length;
        int nFeaturesSubset = (int) Math.sqrt(nFeatures); // 每棵树随机选择 sqrt(n) 个特征
        
        for (int t = 0; t < nTrees; t++) {
            // 有放回抽样(Bootstrap)
            List<Integer> bootstrapIndices = new ArrayList<>();
            for (int i = 0; i < nSamples; i++) {
                bootstrapIndices.add(random.nextInt(nSamples));
            }
            
            // 构建子集
            List<double[]> treeFeatures = new ArrayList<>();
            List<Boolean> treeLabels = new ArrayList<>();
            for (int idx : bootstrapIndices) {
                treeFeatures.add(features.get(idx));
                treeLabels.add(labels.get(idx));
            }
            
            // 训练单棵决策树
            DecisionTree tree = new DecisionTree(maxDepth, minSamplesSplit, random.nextLong());
            tree.train(treeFeatures, treeLabels, nFeaturesSubset);
            trees.add(tree);
        }
    }
    
    /**
     * 预测(投票机制)
     */
    public boolean predict(double[] features) {
        int peakVotes = 0;
        for (DecisionTree tree : trees) {
            if (tree.predict(features)) {
                peakVotes++;
            }
        }
        // 简单多数投票
        return peakVotes > trees.size() / 2;
    }
    
    /**
     * 决策树内部类
     */
    private static class DecisionTree {
        private Node root;
        private int maxDepth;
        private int minSamplesSplit;
        private Random random;
        
        public DecisionTree(int maxDepth, int minSamplesSplit, long seed) {
            this.maxDepth = maxDepth;
            this.minSamplesSplit = minSamplesSplit;
            this.random = new Random(seed);
        }
        
        public void train(List<double[]> features, List<Boolean> labels, int nFeaturesSubset) {
            root = buildTree(features, labels, 0, nFeaturesSubset);
        }
        
        private Node buildTree(List<double[]> X, List<Boolean> y, int depth, int nFeaturesSubset) {
            // 终止条件
            if (depth >= maxDepth || y.size() <= minSamplesSplit || allSame(y)) {
                boolean majorityClass = y.stream().filter(b -> b).count() > y.size() / 2;
                return new Node(majorityClass);
            }
            
            // 选择最佳分割
            Split bestSplit = findBestSplit(X, y, nFeaturesSubset);
            if (bestSplit == null) {
                boolean majorityClass = y.stream().filter(b -> b).count() > y.size() / 2;
                return new Node(majorityClass);
            }
            
            // 分割数据
            List<double[]> XLeft = new ArrayList<>();
            List<Boolean> yLeft = new ArrayList<>();
            List<double[]> XRight = new ArrayList<>();
            List<Boolean> yRight = new ArrayList<>();
            
            for (int i = 0; i < X.size(); i++) {
                if (X.get(i)[bestSplit.featureIndex] <= bestSplit.threshold) {
                    XLeft.add(X.get(i));
                    yLeft.add(y.get(i));
                } else {
                    XRight.add(X.get(i));
                    yRight.add(y.get(i));
                }
            }
            
            Node node = new Node(bestSplit.featureIndex, bestSplit.threshold);
            node.left = buildTree(XLeft, yLeft, depth + 1, nFeaturesSubset);
            node.right = buildTree(XRight, yRight, depth + 1, nFeaturesSubset);
            return node;
        }
        
        private Split findBestSplit(List<double[]> X, List<Boolean> y, int nFeaturesSubset) {
            if (X.size() < 2) return null;
            
            int nFeatures = X.get(0).length;
            List<Integer> featureIndices = new ArrayList<>();
            for (int i = 0; i < nFeatures; i++) featureIndices.add(i);
            Collections.shuffle(featureIndices, random);
            featureIndices = featureIndices.subList(0, Math.min(nFeaturesSubset, nFeatures));
            
            double bestGini = Double.MAX_VALUE;
            Split bestSplit = null;
            
            for (int featIdx : featureIndices) {
                // 获取该特征的所有值并排序
                List<Double> values = X.stream().map(arr -> arr[featIdx]).sorted().collect(ArrayList::new, List::add, List::addAll);
                // 尝试每个可能的分割点
                for (int i = 0; i < values.size() - 1; i++) {
                    double threshold = (values.get(i) + values.get(i + 1)) / 2.0;
                    double gini = computeGiniImpurity(X, y, featIdx, threshold);
                    if (gini < bestGini) {
                        bestGini = gini;
                        bestSplit = new Split(featIdx, threshold);
                    }
                }
            }
            return bestSplit;
        }
        
        private double computeGiniImpurity(List<double[]> X, List<Boolean> y, int featureIndex, double threshold) {
            List<Boolean> left = new ArrayList<>();
            List<Boolean> right = new ArrayList<>();
            
            for (int i = 0; i < X.size(); i++) {
                if (X.get(i)[featureIndex] <= threshold) {
                    left.add(y.get(i));
                } else {
                    right.add(y.get(i));
                }
            }
            
            if (left.isEmpty() || right.isEmpty()) return Double.MAX_VALUE;
            
            double giniLeft = 1.0;
            long trueLeft = left.stream().filter(b -> b).count();
            double pTrue = (double) trueLeft / left.size();
            giniLeft -= pTrue * pTrue + (1 - pTrue) * (1 - pTrue);
            
            double giniRight = 1.0;
            long trueRight = right.stream().filter(b -> b).count();
            double pTrueR = (double) trueRight / right.size();
            giniRight -= pTrueR * pTrueR + (1 - pTrueR) * (1 - pTrueR);
            
            return (left.size() * giniLeft + right.size() * giniRight) / X.size();
        }
        
        public boolean predict(double[] features) {
            return predictNode(root, features);
        }
        
        private boolean predictNode(Node node, double[] features) {
            if (node.isLeaf) {
                return node.prediction;
            }
            if (features[node.featureIndex] <= node.threshold) {
                return predictNode(node.left, features);
            } else {
                return predictNode(node.right, features);
            }
        }
        
        // 内部节点类
        private static class Node {
            boolean isLeaf;
            boolean prediction;
            int featureIndex;
            double threshold;
            Node left, right;
            
            Node(boolean prediction) {
                this.isLeaf = true;
                this.prediction = prediction;
            }
            
            Node(int featureIndex, double threshold) {
                this.isLeaf = false;
                this.featureIndex = featureIndex;
                this.threshold = threshold;
            }
        }
        
        // 分割点
        private static class Split {
            int featureIndex;
            double threshold;
            Split(int featureIndex, double threshold) {
                this.featureIndex = featureIndex;
                this.threshold = threshold;
            }
        }
        
        private boolean allSame(List<Boolean> list) {
            if (list.isEmpty()) return true;
            boolean first = list.get(0);
            return list.stream().allMatch(b -> b == first);
        }
    }
}

3. LSTM 神经网络(长短期记忆)—— 时序之王

import java.util.*;

/**
 * LSTM 神经网络分类器
 * 处理时间序列依赖,适合交通模式识别
 * 使用纯 Java 实现,不依赖外部框架
 */
public class LSTMLayer {
    
    // 权重矩阵 (简化版)
    private double[][] Wi, Wf, Wc, Wo; // 输入门、遗忘门、候选细胞、输出门
    private double[][] Ui, Uf, Uc, Uo; // 循环权重
    private double[] bi, bf, bc, bo;   // 偏置
    
    private int inputSize;
    private int hiddenSize;
    private Random random;
    
    public LSTMLayer(int inputSize, int hiddenSize) {
        this.inputSize = inputSize;
        this.hiddenSize = hiddenSize;
        this.random = new Random(42);
        
        // 初始化权重 (Xavier 初始化)
        double scale = Math.sqrt(2.0 / (inputSize + hiddenSize));
        Wi = initializeMatrix(hiddenSize, inputSize, scale);
        Wf = initializeMatrix(hiddenSize, inputSize, scale);
        Wc = initializeMatrix(hiddenSize, inputSize, scale);
        Wo = initializeMatrix(hiddenSize, inputSize, scale);
        
        Ui = initializeMatrix(hiddenSize, hiddenSize, scale);
        Uf = initializeMatrix(hiddenSize, hiddenSize, scale);
        Uc = initializeMatrix(hiddenSize, hiddenSize, scale);
        Uo = initializeMatrix(hiddenSize, hiddenSize, scale);
        
        bi = initializeVector(hiddenSize, 0.0);
        bf = initializeVector(hiddenSize, 1.0); // 遗忘门初始偏置较大
        bc = initializeVector(hiddenSize, 0.0);
        bo = initializeVector(hiddenSize, 0.0);
    }
    
    private double[][] initializeMatrix(int rows, int cols, double scale) {
        double[][] matrix = new double[rows][cols];
        for (int i = 0; i < rows; i++) {
            for (int j = 0; j < cols; j++) {
                matrix[i][j] = (random.nextDouble() - 0.5) * 2 * scale;
            }
        }
        return matrix;
    }
    
    private double[] initializeVector(int size, double bias) {
        double[] vec = new double[size];
        Arrays.fill(vec, bias);
        return vec;
    }
    
    /**
     * Sigmoid 函数
     */
    private double sigmoid(double x) {
        return 1.0 / (1.0 + Math.exp(-x));
    }
    
    /**
     * Tanh 函数
     */
    private double tanh(double x) {
        return Math.tanh(x);
    }
    
    /**
     * 矩阵向量乘法
     */
    private double[] matVecMul(double[][] mat, double[] vec) {
        int m = mat.length;
        int n = mat[0].length;
        double[] result = new double[m];
        for (int i = 0; i < m; i++) {
            for (int j = 0; j < n; j++) {
                result[i] += mat[i][j] * vec[j];
            }
        }
        return result;
    }
    
    /**
     * 向量加法
     */
    private double[] vecAdd(double[] a, double[] b) {
        double[] c = new double[a.length];
        for (int i = 0; i < a.length; i++) {
            c[i] = a[i] + b[i];
        }
        return c;
    }
    
    /**
     * Hadamard 积 (逐元素乘)
     */
    private double[] hadamard(double[] a, double[] b) {
        double[] c = new double[a.length];
        for (int i = 0; i < a.length; i++) {
            c[i] = a[i] * b[i];
        }
        return c;
    }
    
    /**
     * 前向传播
     * @param inputSequence 时间序列输入 [[t1], [t2], ...]
     * @return 最后一个时间步的隐藏状态
     */
    public double[] forward(List<double[]> inputSequence) {
        double[] h = new double[hiddenSize]; // 隐藏状态
        double[] c = new double[hiddenSize]; // 细胞状态
        
        for (double[] xt : inputSequence) {
            // 输入门
            double[] i = matVecMul(Wi, xt);
            double[] ih = matVecMul(Ui, h);
            double[] it = vecAdd(i, ih);
            it = vecAdd(it, bi);
            double[] i_gate = Arrays.stream(it).map(this::sigmoid).toArray();
            
            // 遗忘门
            double[] f = matVecMul(Wf, xt);
            double[] fh = matVecMul(Uf, h);
            double[] ft = vecAdd(f, fh);
            ft = vecAdd(ft, bf);
            double[] f_gate = Arrays.stream(ft).map(this::sigmoid).toArray();
            
            // 候选细胞状态
            double[] g = matVecMul(Wc, xt);
            double[] gh = matVecMul(Uc, h);
            double[] gt = vecAdd(g, gh);
            gt = vecAdd(gt, bc);
            double[] cand_c = Arrays.stream(gt).map(this::tanh).toArray();
            
            // 更新细胞状态
            c = hadamard(f_gate, c); // 遗忘
            c = vecAdd(c, hadamard(i_gate, cand_c)); // 输入
            
            // 输出门
            double[] o = matVecMul(Wo, xt);
            double[] oh = matVecMul(Uo, h);
            double[] ot = vecAdd(o, oh);
            ot = vecAdd(ot, bo);
            double[] o_gate = Arrays.stream(ot).map(this::sigmoid).toArray();
            
            // 新的隐藏状态
            h = hadamard(o_gate, Arrays.stream(c).map(this::tanh).toArray());
        }
        
        return h; // 返回最终隐藏状态
    }
}

/**
 * 基于 LSTM 的交通分类器
 */
public class LSTMClassifier {
    
    private LSTMLayer lstm;
    private double[] outputWeights; // 全连接层权重
    private double outputBias;
    private int sequenceLength = 6; // 使用过去6个时间步
    private int inputSize; // 特征维度
    private int hiddenSize = 16;
    private FeatureExtractor featureExtractor;
    
    public LSTMClassifier(FeatureExtractor featureExtractor, int inputSize) {
        this.featureExtractor = featureExtractor;
        this.inputSize = inputSize;
        this.lstm = new LSTMLayer(inputSize, hiddenSize);
        this.outputWeights = new double[hiddenSize];
        this.outputBias = 0.0;
        // 初始化
        Random rand = new Random(42);
        for (int i = 0; i < hiddenSize; i++) {
            outputWeights[i] = (rand.nextDouble() - 0.5) * 0.1;
        }
    }
    
    /**
     * 训练(简化版,仅演示结构)
     */
    public void train(List<List<TrafficSnapshot>> sequences, List<Boolean> labels) {
        // 实际训练需要反向传播、梯度下降等
        // 此处省略,重点展示架构
        System.out.println("[LSTM] Training on " + sequences.size() + " sequences...");
        // ... (真实项目应集成 DL4J 或 TensorFlow Java)
    }
    
    /**
     * 预测
     */
    public boolean predict(List<TrafficSnapshot> recentHistory) {
        // 提取特征序列
        List<double[]> featureSeq = new ArrayList<>();
        for (TrafficSnapshot snap : recentHistory) {
            double[] features = featureExtractor.extractFeatures(snap, null);
            featureSeq.add(features);
        }
        
        // 截断或填充到固定长度
        while (featureSeq.size() > sequenceLength) {
            featureSeq.remove(0);
        }
        while (featureSeq.size() < sequenceLength) {
            // 填充默认值(如零向量)
            featureSeq.add(new double[inputSize]);
        }
        
        // LSTM 前向传播
        double[] hiddenState = lstm.forward(featureSeq);
        
        // 全连接 + Sigmoid
        double output = outputBias;
        for (int i = 0; i < hiddenState.length; i++) {
            output += outputWeights[i] * hiddenState[i];
        }
        double prob = 1.0 / (1.0 + Math.exp(-output));
        
        return prob >= 0.5;
    }
}

五、核心分类引擎:整合一切

import java.time.LocalDateTime;
import java.util.*;

/**
 * 交通模式分类引擎
 * 整合数据、特征、模型,提供统一接口
 */
public class TrafficPatternClassifier {
    
    private List<TrafficSnapshot> historicalData;
    private FeatureExtractor featureExtractor;
    private LogisticRegressionClassifier lrModel;
    private RandomForestClassifier rfModel;
    private LSTMClassifier lstmModel;
    private boolean modelsTrained = false;
    
    public TrafficPatternClassifier() {
        this.historicalData = new ArrayList<>();
        this.featureExtractor = new FeatureExtractor();
        this.lrModel = new LogisticRegressionClassifier();
        this.rfModel = new RandomForestClassifier();
        // this.lstmModel = new LSTMClassifier(featureExtractor, expectedFeatureSize);
    }
    
    /**
     * 添加历史数据(用于训练和特征提取)
     */
    public void addHistoricalData(TrafficSnapshot snapshot) {
        historicalData.add(snapshot);
        // 限制历史数据大小,避免内存溢出
        if (historicalData.size() > 10000) {
            historicalData.remove(0);
        }
    }
    
    /**
     * 训练所有模型
     */
    public void trainModels() {
        if (historicalData.size() < 100) {
            throw new IllegalStateException("Not enough data to train");
        }
        
        List<double[]> features = new ArrayList<>();
        List<Boolean> labels = new ArrayList<>();
        
        for (TrafficSnapshot snap : historicalData) {
            double[] feat = featureExtractor.extractFeatures(snap, historicalData);
            features.add(feat);
            labels.add(snap.isPeakHour());
        }
        
        // 训练逻辑回归
        lrModel.train(features, labels);
        
        // 训练随机森林
        rfModel.train(features, labels);
        
        // 训练 LSTM (需要序列数据)
        // trainLSTMModel();
        
        modelsTrained = true;
        System.out.println("All models trained successfully.");
    }
    
    /**
     * 预测单个快照是否为高峰
     * 使用随机森林作为主模型
     */
    public ClassificationResult classify(TrafficSnapshot current) {
        if (!modelsTrained) {
            throw new IllegalStateException("Models not trained yet");
        }
        
        double[] features = featureExtractor.extractFeatures(current, historicalData);
        
        boolean rfPrediction = rfModel.predict(features);
        double lrProbability = lrModel.predictProbability(features);
        
        return new ClassificationResult(
            current.getTimestamp(),
            current.getRoadSegmentId(),
            rfPrediction,
            lrProbability,
            features
        );
    }
    
    /**
     * 分类结果封装
     */
    public static class ClassificationResult {
        private LocalDateTime timestamp;
        private String roadSegmentId;
        private boolean isPeakHour;
        private double confidence; // 来自逻辑回归的概率
        private double[] featuresUsed;
        
        public ClassificationResult(LocalDateTime timestamp, String roadSegmentId, 
                                  boolean isPeakHour, double confidence, double[] featuresUsed) {
            this.timestamp = timestamp;
            this.roadSegmentId = roadSegmentId;
            this.isPeakHour = isPeakHour;
            this.confidence = confidence;
            this.featuresUsed = featuresUsed;
        }
        
        // Getters...
        public LocalDateTime getTimestamp() { return timestamp; }
        public String getRoadSegmentId() { return roadSegmentId; }
        public boolean isPeakHour() { return isPeakHour; }
        public double getConfidence() { return confidence; }
        public double[] getFeaturesUsed() { return featuresUsed; }
        
        @Override
        public String toString() {
            return String.format("Classification{time=%s, road=%s, peak=%s, conf=%.2f}",
                    timestamp, roadSegmentId, isPeakHour, confidence);
        }
    }
}

六、数据模拟与测试

import java.time.DayOfWeek;
import java.time.LocalDateTime;
import java.util.Random;

/**
 * 交通数据模拟器
 * 生成逼真的交通快照流
 */
public class TrafficDataSimulator {
    
    private Random random;
    private String[] segments = {"A1", "B2", "C3", "D4"};
    private LocalDateTime currentTime;
    
    public TrafficDataSimulator() {
        this.random = new Random(42);
        this.currentTime = LocalDateTime.now().withMinute(0).withSecond(0).withNano(0);
    }
    
    /**
     * 生成下一个时间步的快照
     */
    public TrafficSnapshot nextSnapshot() {
        currentTime = currentTime.plusMinutes(15); // 每15分钟一个数据点
        
        String segment = segments[random.nextInt(segments.length)];
        
        // 模拟高峰时段(工作日早晚高峰)
        boolean isWeekday = currentTime.getDayOfWeek() != DayOfWeek.SATURDAY && 
                           currentTime.getDayOfWeek() != DayOfWeek.SUNDAY;
        boolean isMorningPeak = isWeekday && currentTime.getHour() >= 7 && currentTime.getHour() <= 9;
        boolean isEveningPeak = isWeekday && currentTime.getHour() >= 17 && currentTime.getHour() <= 19;
        boolean isPeak = isMorningPeak || isEveningPeak;
        
        // 基础速度
        double baseSpeed = isPeak ? 25.0 : 50.0;
        // 添加随机波动
        double speed = baseSpeed + random.nextGaussian() * 10.0;
        speed = Math.max(5.0, Math.min(80.0, speed)); // 限幅
        
        int vehicleCount = (int)(speed < 30 ? 400 + random.nextInt(100) : 200 + random.nextInt(150));
        double occupancyRate = 1.0 - speed / 80.0;
        occupancyRate = Math.max(0.1, Math.min(0.95, occupancyRate));
        
        WeatherCondition weather = WeatherCondition.CLEAR;
        if (random.nextDouble() < 0.1) weather = WeatherCondition.RAIN;
        
        boolean isHoliday = false;
        TrafficEvent event = TrafficEvent.NONE;
        if (random.nextDouble() < 0.02) event = TrafficEvent.ACCIDENT;
        
        return new TrafficSnapshot(currentTime, segment, speed, vehicleCount, 
                                 occupancyRate, weather, isHoliday, event);
    }
}

七、终极测试:一周交通模拟

/**
 * 主测试类
 */
public class TrafficClassificationTest {
    
    public static void main(String[] args) throws InterruptedException {
        System.out.println("=== 交通模式分类器综合测试 ===");
        
        TrafficDataSimulator simulator = new TrafficDataSimulator();
        TrafficPatternClassifier classifier = new TrafficPatternClassifier();
        
        // 模拟一周数据(每15分钟一个点,共 7*24*4 = 672 个点)
        System.out.println("Generating one week of traffic data...");
        for (int i = 0; i < 672; i++) {
            TrafficSnapshot snapshot = simulator.nextSnapshot();
            classifier.addHistoricalData(snapshot);
        }
        
        // 训练模型
        classifier.trainModels();
        
        // 测试最近10个样本
        System.out.println("\n=== 实时预测结果 ===");
        for (int i = 0; i < 10; i++) {
            TrafficSnapshot current = simulator.nextSnapshot();
            TrafficPatternClassifier.ClassificationResult result = classifier.classify(current);
            System.out.println(result);
            
            // 模拟实时流
            Thread.sleep(100);
        }
        
        System.out.println("\n测试完成。");
    }
}

八、总结:Java 的交通智能革命

通过以上深度实现,我们看到了 Java 如何从“企业胶水”蜕变为“城市大脑”:

  • 完整数据闭环:从模拟 → 特征 → 模型 → 预测。
  • 多模型融合:LR(可解释)、RF(鲁棒)、LSTM(时序)各司其职。
  • 生产级设计:考虑内存、性能、可维护性。
  • 领域知识注入:拥堵指数、历史对比等专业特征。

这不仅是分类,更是用代码理解城市心跳

真正的智能交通,不在于预测准确率提升1%,而在于让每一次出行都更接近“零拥堵”的理想。

现在,轮到你了。试着加入深度强化学习优化信号灯,或图神经网络分析路网拓扑,让你的交通系统更进一步。

Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐