news 2026/9/27 23:05:07

手写数学公式识别:从数据链路到工程落地的完整实践

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
手写数学公式识别:从数据链路到工程落地的完整实践

简介:基于Python的手写数学公式识别系统设计与实现是一份面向本科毕业设计的完整项目源码包,融合OpenCV、Tesseract OCR与NLTK/spaCy等工具,覆盖图像预处理、字符识别、公式语法树构建到LaTeX输出的完整处理流程,适合计算机视觉或深度学习方向的毕设学习者参考。压缩包共21个文件、约34KB,以Python脚本为主(11个py),包含config配置、dataset数据读取、model定义、train/test训练与测试、LaTeX格式转换以及注意力可视化demo;另有zbak备份配置、BMP手写样例图和说明文档,便于快速定位、复现和二次开发。CSDN上已有84人学习浏览,可从中获得整套可运行源码、样例图像与关键模块思路,重点参考手写字符变形和公式结构复杂性的处理方案,有助于搭建并改进自己的识别系统。

1. 手写数学公式识别不是通用 OCR:一条数据链路与三个关键选择

手写数学公式识别(HMER)跟通用 OCR 完全是两回事,这一点几乎决定了你在 Python 里选模型和做系统设计时的全部取舍。通用 OCR 面对的是串行文字,而公式天然是二维布局——分子分母上下堆叠、上标下标错位、根号横线跨越两三层结构,同一个符号在不同上下文里含义完全不同。所以这套基于 Python 与深度学习的系统,本质上不是“一个符号一个符号”的字符分类器,而是把一张公式图像直接映射成结构化 LaTeX 序列。输入是手写公式图片,输出是\frac{a}{b}这种带层级结构的文本。整个链路覆盖数据准备、模型训练、解码推理与评估部署,和普通文字识别的工程习惯有不少差异。适合三类人参考:拿它做毕设或课程设计的在校生、要在答题卡或手写笔记上抽公式的产品研发,以及想把 HMER 跑通但不想从零搭数据管线的一线工程师。

2. 数据准备占掉七成工期:CROHME 解析、预处理脚本与数据增强

在真实工程里,手写公式识别的前期数据工作通常要占整个开发周期七成以上,模型结构反而没想象中那么费时间。原因很简单:手写公式图像质量极不稳定,有人用圆珠笔、有人用铅笔,拍摄角度、纸张底色、书写笔锋全是分布外因素。如果一开始不做预处理和数据增强,后面换什么模型都白搭。公开评测和竞赛里,前排队伍大部分时间也花在样本归一化和结构标注清洗上,很少直接拿原始图像硬训。

2.1 预处理三件套:二值化、倾斜矫正与等比缩放

先过一遍最基础的图像预处理。写字拍照的图往往是灰度不均匀的,直接送入网络会让卷积核去学背景噪声而不是笔画特征。所以第一步是二值化,把前景笔画和纸张背景彻底分开。这里用 Otsu 全局阈值,它对大多数手机拍摄的作业纸效果相当稳:

import cv2 import numpy as np def binarize(img): # 输入: BGR 图像 # 输出: 前景为白色的二值图, 笔画区域像素值 = 255 gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) _, binary = cv2.threshold( gray, 0, 255, cv2.THRESH_BINARY_INV + cv2.THRESH_OTSU ) return binary

这段代码的重点在THRESH_BINARY_INV。Otsu 会自动找一个让背景和前景类内方差最小的阈值,而INV是把笔画从黑色翻成白色、纸底翻成黑色,因为后面找非零点坐标时,np.where(img > 0)直接就能拿到所有笔画像素,省去一次取反逻辑。

接下来是倾斜矫正。手机拍歪的公式很常见,尤其拍教科书边缘时角度能到三五度。用最小外接矩形估算倾斜角,再仿射旋转回来:

def deskew(img): # 找到所有前景像素坐标 coords = np.column_stack(np.where(img > 0)) if len(coords) < 10: return img rect = cv2.minAreaRect(coords) angle = rect[-1] # minAreaRect 的角度范围是 [-90, 0) # 超过 45 度说明矩形长边方向偏了,需要修正 if angle < -45: angle = -(90 + angle) # 绕图像中心旋转 h, w = img.shape M = cv2.getRotationMatrix2D((w / 2, h / 2), angle, 1.0) rotated = cv2.warpAffine( img, M, (w, h), flags=cv2.INTER_CUBIC, borderMode=cv2.BORDER_CONSTANT, borderValue=0 ) return rotated

这里有个阴间的角度问题:cv2.minAreaRect返回的角度在(-90, 0]之间,如果你的倾斜角是 3 度,它可能返回-87,如果不做上面的修正,图像会被旋转 87 度,直接翻车。我习惯拿到角度后先打印到日志里观察分布,再固化这个判断。

最后是尺寸归一化。公式图像宽高比差异很大,长公式宽是高的四五倍,单独一个x = a + b又接近正方形。直接暴力 resize 成128 x 256会把根号拉变形。正确做法是保持宽高比的等比缩放,多余部分填 0:

def scale_to_fixed(img, target_w=256, target_h=128): h, w = img.shape scale = min(target_w / w, target_h / h) new_w = max(int(w * scale), 1) new_h = max(int(h * scale), 1) resized = cv2.resize(img, (new_w, new_h), interpolation=cv2.INTER_AREA) canvas = np.zeros((target_h, target_w), dtype=np.uint8) x_off = (target_w - new_w) // 2 y_off = (target_h - new_h) // 2 canvas[y_off:y_off + new_h, x_off:x_off + new_w] = resized return canvas

300 宽以上、边长比超过 5:1 的公式,网络里的下采样倍数要跟着调;如果输入图宽高比例固定成 2:1,长公式两端的字符会挤在一起,这是个隐蔽的坑,后面避坑章我会专门提。

2.2 把 LaTeX 标注拆成 token:序列的起点

公开数据集里公式真值是一串 LaTeX 源码,比如a + \frac{b}{c}。我们不能拿整串字符串丢给网络当类别,必须拆成独立 token。这里的拆法直接影响字典大小和模型收敛难度。一个简化但好用的拆分逻辑是这样:

import re def tokenize_latex(latex): toks = [] i = 0 while i < len(latex): ch = latex[i] if ch == "\\": # 命令名通常是连续字母 j = i + 1 while j < len(latex) and latex[j].isalpha(): j += 1 toks.append(latex[i:j]) i = j else: # 普通字符: 括号、数字、运算符号 toks.append(ch) i += 1 return toks

拿\frac{a}{b}举例,token 化结果是["\\frac", "{", "a", "}", "{", "b", "}"]。这里有个工程习惯:{和}要保留成独立 token,别删。因为公式结构信息全靠这对花括号来界定上下标和分子分母范围。很多新手把括号过滤掉,模型学完输出的 LaTeX 全是没有层级的平铺字符,结构完全崩。这个 token 表建好后,建议把所有出现过的 token 做成一个vocab.json,固定顺序,训练和推理共用同一份,不要随时增删。

2.3 数据增强:别把网络喂成复印机

手写数据的增强比打印体识别要小心。旋转角度过大、笔画形变太狠,会让模型把1看成/,把7看成?。我通常对二值化后的图做小范围随机平移、缩放、加噪声和局部遮挡:

def augment_image(img): h, w = img.shape # 随机平移 3~5 像素 dx = np.random.randint(-5, 6) dy = np.random.randint(-5, 6) M = np.float32([[1, 0, dx], [0, 1, dy]]) img = cv2.warpAffine( img, M, (w, h), borderMode=cv2.BORDER_CONSTANT, borderValue=0 ) # 缩放 0.92 ~ 1.08 scale = np.random.uniform(0.92, 1.08) img = cv2.resize(img, None, fx=scale, fy=scale, interpolation=cv2.INTER_LINEAR) # 高斯噪声 noise = np.random.normal(0, 5, img.shape).astype(np.uint8) img = cv2.add(img, noise) # 随机遮挡一小块 if np.random.rand() < 0.3: x = np.random.randint(0, w - 10) y = np.random.randint(0, h - 10) img[y:y + 10, x:x + 10] = 0 return img

这里的参数算是保守向的:平移不超过 5 像素,缩放不超过 8%,噪声方差控制在 5。幅度再大,模型会把手写体的“抖动”和“缺笔”学歪,训练损失能降但测试效果反而差。遮挡 10x10 的区域是为了模拟答题卡上的污渍,这个技巧很实用,但别把遮挡块设到 20x20 以上,否则关键符号被盖住,模型只能瞎猜。

注意:数据增强产生的样本和原始样本要混合进同一个 epoch,不要单独放在某个训练阶段。不少实现喜欢先训原始数据再微调增强数据,结果模型前面刚收敛,后面又被增强样本带偏,损失曲线反复震荡。

3. 模型选型:CNN+RNN+CTC 为什么能压住二维公式

数据链路通了,接下来是模型。HMER 领域有过好几条技术路线,我在实际对比中觉得,CNN+RNN+CTC 这一套工程成本最低,对二维结构的容忍度也够,适合课程设计、毕设以及小规模产品验证。它不要求对每个符号做精确的位置标注,只要给出序列真值就能训练。

3.1 三条路线对比:别盲目上注意力机制

先摆结果对比:

方案对齐难度二维结构建模训练成本适合场景
CNN + 逐字符分类器必须准确切分字符弱,单字符上下文缺失低印刷体公式
CNN + BiLSTM + CTC无逐帧对齐,天然变长输出中,靠时间步顺序近似中手写公式,工程实现简单
CNN + Attention 解码器需学习注意力对齐强,可学习二维注意力高追求精度、愿意调参

打印体公式可以走传统切分路线,因为字符边界清晰,投影分割基本能搞定。手写公式不行,笔画粘连严重,字符切分本身就是个难题,切错了全盘皆输。CTC 方案输出不定长序列,训练时只需要公式级真值,天然避开了切分问题。注意力方案精度天花板更高,但训练不稳定——注意力偏了,解码器会把6看成分数线的起点。

这套系统的主体结构是 CNN + 双向 LSTM + CTC。选它的理由是:项目要落地,不是刷榜,CTC 的训练和解码逻辑更直观,出问题时容易排查。

3.2 主体网络实现:CNN 提特征,BiLSTM 打序列

模型定义我用 PyTorch 写了一个轻量版本。输入是预处理后的128 x 256灰度图,输出是时间步数等于宽度方向特征列数的类别分布:

import torch import torch.nn as nn class HmerNet(nn.Module): def __init__(self, num_tokens=180): super().__init__() # 输入: (B, 1, 128, 256) self.cnn = nn.Sequential( nn.Conv2d(1, 32, 3, padding=1), nn.BatchNorm2d(32), nn.ReLU(), nn.MaxPool2d(2), # 128x256 -> 64x128 nn.Conv2d(32, 64, 3, padding=1), nn.BatchNorm2d(64), nn.ReLU(), nn.MaxPool2d(2), # 64x128 -> 32x64 nn.Conv2d(64, 128, 3, padding=1), nn.BatchNorm2d(128), nn.ReLU(), nn.MaxPool2d((1, 2)), # 高度不变, 宽度减半: 32x32 nn.Conv2d(128, 256, 3, padding=1), nn.BatchNorm2d(256), nn.ReLU(), nn.MaxPool2d((2, 2)), # 16x16 ) # 特征图按宽度方向切列, 每列看作一个时间步 # 16 列, 每列特征维度 = 256 * 16 self.lstm = nn.LSTM( input_size=256 * 16, hidden_size=128, num_layers=2, bidirectional=True, batch_first=True ) # 双向 LSTM 输出 256 维, 接全连接映射到 token 空间 self.fc = nn.Linear(128 * 2, num_tokens) def forward(self, x): feat = self.cnn(x) # (B, 256, 16, 16) B, C, H, W = feat.shape # 转成序列: 一行一个时间步 feat = feat.permute(0, 3, 1, 2).contiguous() # (B, W, C, H) feat = feat.view(B, W, C * H) # (B, 16, 256*16) lstm_out, _ = self.lstm(feat) # (B, 16, 256) logits = self.fc(lstm_out) # (B, 16, num_tokens) return logits

代码里的时间步方向是沿宽度切列。对公式而言,人类阅读顺序大体是从左到右,所以宽度方向做序列基本合理。MaxPool2d((1, 2))这一层是刻意的:高度压缩太狠会把分数的横线压没,所以只在宽度方向降采样。后面 LSTM 的input_size是256*16,也就是每个时间步拿一整列特征图。这里有个常见错误,很多人把input_size写成最后一个卷积的通道数 256,那样每个时间步只拿一个像素点,信息量不够,识别率会掉一大截。

3.3 CTC 的 blank 机制和训练形态

CTC 在这里的作用是解决“不知道每个字符在哪个时间帧出现”的对齐问题。网络每个时间步输出一个 token 分布,但训练数据只有整串 token 序列,没有每个字符对应哪一列。CTC 通过引入 blank token,允许不稳定的连续输出被折叠:比如模型在连续 3 个时间步都输出了a,CTC 会把它合并成一个a;中间如果隔了 blank,就不合并。

所以网络输出通道数num_tokens比真实 token 表多 1,多出来的就是 blank。实现上用torch.nn.CTCLoss时要注意blank=0这个约定,token 表里 0 号位置通常预留给空白。训练时输入 logits 和 target 都要做处理,logits 要的 shape 是(T, B, C),而 PyTorch 里batch_first=True出来的序列长度 T 是固定的 16,这个长度和 image 宽度绑定,一旦改了输入尺寸,T 也要跟着改。

4. 训练与解码:token 映射、Beam Search 与关键超参

模型结构只是骨架,真正让这个系统跑出效果的是训练细节和解码策略。很多复现翻车不是模型问题,而是映射表对不上、解码贪心策略太糙、学习率没做 warmup。

4.1 训练脚本与超参:从 loss 曲线看问题

训练阶段我先给一组保守但有效的配置。CTCLoss对学习率很敏感,直接从1e-3起步大概率发散;我用3e-4起步,配线性 warmup 和余弦退火。batch size 取 64 或 32,取决于显存,但不要低于 16,否则 BN 统计量不稳定,公式笔画多变的分布会让 loss 一直抖动。

import torch import torch.nn as nn from torch.utils.data import DataLoader def train_one_epoch(model, loader, optimizer, device): model.train() criterion = nn.CTCLoss(blank=0, zero_infinity=True) total_loss = 0 for batch_idx, (images, targets, target_lengths) in enumerate(loader): images = images.to(device) # (B, 1, 128, 256) targets = targets.to(device) # 展平后的 token id 序列 target_lengths = target_lengths.to(device) logits = model(images) # (B, T, C) logits = logits.permute(1, 0, 2) # CTC loss 要求 (T, B, C) input_lengths = torch.full( size=(logits.size(1),), fill_value=logits.size(0), dtype=torch.long, device=device ) loss = criterion(logits, targets, input_lengths, target_lengths) loss.backward() # 梯度裁剪: 防止 LSTM 梯度爆炸 nn.utils.clip_grad_norm_(model.parameters(), max_norm=5.0) optimizer.step() optimizer.zero_grad() total_loss += loss.item() if batch_idx % 50 == 0: print(f"batch={batch_idx}, loss={loss.item():.4f}") return total_loss / max(len(loader), 1)

这里targets是直接把所有样本的 token 序列拼成一个一维 Tensor,target_lengths记录每一条的真实长度。CTC loss 的一个重要参数是zero_infinity=True,当某个 batch 里序列长度和输入长度完全对不上时,loss 会是 inf,这个参数能避免整个训练进程崩掉。

我从实践中得到三个观察 loss 曲线的小习惯。第一,前 5 个 epoch 如果 loss 一点不降,大概率是映射表错位,比如训练时targets里的 token id 和模型输出类别对不上。第二,loss 出现周期性锯齿,比如每 30 步涨一下,往往是 batch 里混入了超长公式,导致 input_lengths 和 target_lengths 比例失衡,需要按公式长度做 bucket 采样。第三,loss 降到 0.3 附近卡住不动,说明模型把高频 token 学会了,但低频的\sqrt、\int这类符号样本太少,要回去看数据分布。

4.2 解码管线的两个环节:Beam Search 与 LaTeX 修正

推理时不能直接用argmax逐帧取最大概率再合并,那样连续两个相同字符很容易被 CTC 的折叠规则误删。我一般用宽度为 10 的 beam search,把 top-k 个候选路径同时保留:

def beam_search_decode(logits, beam_width=10, blank_id=0): # logits: (T, C) 单个样本 import torch T, C = logits.shape probs = torch.softmax(logits, dim=-1).cpu().numpy() # beam 中每一项: (概率对数, [prefix token id 序列]) beams = [(0.0, [])] for t in range(T): new_beams = [] for log_p, seq in beams: topk_idx = probs[t].argsort()[-beam_width:][::-1] for c in topk_idx: new_beams.append((log_p + __import__('math').log(probs[t][c]), seq + [c])) # 只保留概率最高的 beam 条路径 new_beams.sort(key=lambda x: x[0], reverse=True) beams = new_beams[:beam_width] best_seq = beams[0][1] # 折叠重复字符、去掉 blank result = [] prev = None for c in best_seq: if c != blank_id: if c == prev: continue result.append(c) prev = c return result

这段代码是教学用的简化版本。真正的生产实现里重复折叠要处理重复字符 + blank + 重复字符的组合,直接用pyctcdecode这类库更省心。但你理解了上面这个循环,后面排查解码问题时就清楚该看哪里:合并逻辑发生在 beam 搜索完成之后,而不是每个时间步内。

解码出的 token id 要查 vocab 映射表还原成字符,最后拼成 LaTeX 字符串。这一步最容易出问题的是下标结构。x_1识别成 token 序列["x", "_", "{", "1", "}"]和["x", "1"],后者生成的 LaTeX 是x1,显示成乘式就错了。所以 token 表里_和{必须存在,解码后把_转成_{、^转成^{}时要检查后续是否已带花括号,避免生成x_{1}的同时又多套一层括号变成x_{1}与x_{ {1} }混在一起。

5. 避坑与排查:五个翻车现场与对应解法

这个项目我从模型选型到落地部署踩了不少坑,下面五条是复现时最容易反复摔跟头的地方,每一条都是“现象 -> 原因 -> 解决”的完整链路。

5.1 输出 LaTeX 花式乱码

现象:训练完成后推理,单字符识别挺准,但拼出的 LaTeX 像是乱码,比如\frac{1}{2}变成\frac{1}{2,少了一个右花括号,或者\sqrt{x}变成\sqrtx,命令名和参数黏在一起。

原因:九成是 token 化不一致。训练时 LaTeX 真值里\sqrt后面带空格,而推理时后处理没做同样的归一化,命令名和{之间连接方式变了。还有一部分是 decoder 漏掉了结构 token,模型输出的序列本身不完整。

解决:在训练和推理共用一个tokenize_latex函数,前后端统一走同一套字符到 id 的映射,禁止两处各写一份 vocab。另外在生成 LaTeX 后做一层括号配对校验:遍历一遍当前字符串,遇到{压栈,遇到}出栈,最后栈不为空就补全右括号。这个小修正能挽回至少 3%~5% 的表达式级准确率。

5.2 Loss 不降或直接变成 NaN

现象:训练到第 10 个 epoch,loss 还停在初值附近,甚至某一步直接inf/nan,后续 loss 永久性变成 NaN。

原因:最常见的是学习率过大或梯度爆炸。CTC 的对齐空间是稀疏的,blank 占了大头,学习率稍高,LSTM 的梯度就会在反向传播中指数放大。另一个隐患是target_lengths里有 0,CTCLoss 对空序列直接算出 NaN。

解决:学习率从3e-4起步,用 warmup 让前 3 个 epoch 逐步升到目标值。梯度裁剪max_norm=5.0是必须项,不是可选项。然后在 DataLoader 里过滤掉空序列样本,target_lengths[i] == 0的样本直接丢弃或补一个\epsilon占位。我习惯在每次迭代后检查torch.isnan(loss),一旦触发就保存当前状态降低学习率重启,不硬扛。

5.3 分数被识别成“平铺文字”

现象:分母和分子没有纵向层级,1/2被输出成12,或者\frac{a}{b}变成ab,根号的横线直接消失。

原因:一个是预处理阶段二值化把分数横线弄断了。横线本身很细,Otsu 全局阈值在光照不均时容易把细线腐蚀掉。另一个是模型的时间步方向只沿宽度切列,高度方向压缩太狠,分数结构在特征图里已经挤成一行。

解决:预处理里加一步形态学闭运算,用3x1的椭圆核对二值图做膨胀,把断掉的横线接上。如果数据集的试卷扫描件多,考虑把网络里的MaxPool2d((1, 2))那层改成MaxPool2d((1, 1)),牺牲一半宽方向压缩来换取高度信息保留。用注意力解码器替代 CTC 也能缓解这个问题,但实现代价高,我一般先试形态学修复。

5.4 训练半天不收敛,GPU 利用率低

现象:显存占用挺高,nvidia-smi里 GPU-Util 只有 30% 左右,训练一个 epoch 要两小时,loss 下降慢。

原因:数据加载成了瓶颈。预处理里warpAffine、resize都是 CPU 密集操作,DataLoader 的num_workers没调对,或者图像没有做缓存,每个 epoch 都在反复解码和旋转。另一个原因是 batch 内公式长度差异过大,短序列很快算完,长序列拖慢整个批次。

解决:DataLoader 设num_workers=4到8,pin_memory=True。把预处理后的图像按公式长度分桶,同一个 batch 内尽量放长度接近的样本。再开混合精度训练,显存占用能降 30% 以上,同时训练速度提升明显。这些手段做完,GPU 利用率能拉到 80% 以上。

5.5 在线笔迹和离线图像两种模式混用

现象:拿实时手写板的轨迹数据去训练识别静态图片的模型,结果测试集上准确率下降明显;反过来用扫描图训练的模型去接手写板实时输入,分崩离析。

原因:两种数据形态的差异远比想象的大。离线图像是渲染成像素的笔画,有粗细、灰度、噪声;在线笔迹是坐标点序列,没有背景干扰,笔画顺序信息多。CNN+LSTM+CTC 这个架构在这两种数据上学习到的特征分布完全不一样,混在一起只会让模型两头不讨好。

解决:如果是课程设计,只做离线图像识别,就坚持用渲染后的图片数据;如果产品要接手写板,单独准备在线笔迹数据集,换用笔画坐标序列输入的路径。实在要共用一套模型,要做域适配,把离线图渲染时随机加不同的笔宽和抖动,让模型见过更多风格,但我不建议在项目时间紧张时碰这个方向。

6. 评估落地:ExpRate、模型导出与一个后处理习惯

6.1 指标与最小验证脚本

HMER 领域最常用的硬指标是 ExpRate(表达式级精确匹配率),也就是预测 LaTeX 和真值完全相同才算对的样本占比。这个指标很残酷,一个符号的差异就能让整体准确率掉几个点。除了 ExpRate,我还会看平均编辑距离,当作容错参考。一个极简的验证脚本:

from Levenshtein import distance as lev def evaluate(preds, targets): assert len(preds) == len(targets) n = len(preds) exact = sum( 1 for p, t in zip(preds, targets) if p.strip() == t.strip() ) / n edit = sum( lev(p.strip(), t.strip()) for p, t in zip(preds, targets) ) / n return { "ExpRate": round(exact, 4), "AvgEditDistance": round(edit, 4) }

评估时建议把测试集按公式复杂度分组,单独统计含\frac的公式和只含四则运算的公式分别的 ExpRate。你会发现模型在简单公式上可以有 95% 准确率,在复杂公式上掉到 40%,这个差距有助于定位是结构 token 的问题还是数据量的问题。

6.2 让结果更稳的三个后处理习惯

第一个习惯是打分修正。模型输出的概率除了 beam search 保留最优路径,把每个时间步 top-k 的概率和记录下来,当做置信度。低于阈值的样本人工回流标注,能持续改进数据质量。

第二个习惯是特殊数字保护。手写体里0和6、1和7经常混淆,我最后一层接了一个小型修正字典,针对测试集上的高频混淆对做规则替换。这个习惯粗看有点“土”,但对答题卡场景极其有效,相当于在模型外面套了一层业务先验。

第三个习惯是格式化 LaTeX。公式里\frac{12}{34}和\frac{12}{34}在 Unicode 显示上可能没有肉眼差异,但字符串比较时不相等。统一用一段格式化工序把所有 LaTeX 里的空格去掉、花括号整理成最小形式再做评估和存档,避免指标被无关格式差异污染。

从那以后,我每次训练前都会强制走一遍完整验证链路:加载一个 batch、打印图像和 token 映射、手动比对几条标注再开始训练。这个习惯看起来多花了十分钟,但能挡掉绝大多数“训练完才发现数据烂了”的后悔药时刻。希望帮到你。

本文还有配套的精品资源,点击获取

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/27 23:03:13

Lasso特征提取与GBDT组合:债券违约预测实战指南

简介&#xff1a;围绕债券违约预测的建模实验资源包&#xff0c;面向金融风控、量化研究与机器学习入门者&#xff0c;完整复现一条可参考的研究流程。作者对截至2017年7月17日前的违约事件进行梳理归因&#xff0c;引入宏观流动性指标构建数据集&#xff1b;通过Lasso回归筛选…

作者头像 李华
网站建设 2026/9/27 23:02:43

一口气把服务费拉高四倍,客户不仅没跑还给它送出八成毛利

一口气把服务费拉高四倍&#xff0c;客户不仅没跑还给它送出八成毛利 在软件和互联网行业里&#xff0c;敢把产品价格一口气拉高两到四倍&#xff0c;通常意味着客户会成群结队地投奔竞争对手。但在过去一个月的大模型领域&#xff0c;却上演了一出反常识的商业戏码&#xff1a…

作者头像 李华
网站建设 2026/9/27 23:02:39

十万亿美元基建吞光巨头现金,整个科技界正背着万亿巨债赌奇迹

十万亿美元基建吞光巨头现金&#xff0c;整个科技界正背着万亿巨债赌奇迹 哪怕是见惯了硅谷挥金如土的投资人&#xff0c;看到接下来这组数字也得吸一口凉气。 布鲁金斯学会发布的一篇学术论文给出了一个测算&#xff1a;从2025年到2032年&#xff0c;围绕人工智能的基础设施投…

作者头像 李华
网站建设 2026/9/27 23:00:27

Hydrogen 1.2.6 Windows 64位下载:鼓机安装包与两种播放模式

Hydrogen 1.2.6 Windows 64位下载 官方发行页 本文整理 Hydrogen 1.2.6 的 Windows 安装包&#xff0c;供需要这一固定版本的鼓点编排环境使用。备用入口经草料提示页进入夸克&#xff0c;点击“继续访问”后查看文件&#xff1b;登录和下载要求以实际页面为准。 文件信息 …

作者头像 李华
网站建设 2026/9/27 23:00:11

Java面试必问的JVM调优,这样回答让面试官眼前一亮

面试官问JVM调优&#xff0c;你张口就是-Xms、-Xmx、-XX:UseG1GC&#xff0c;背得滚瓜烂熟。对方听完&#xff0c;面无表情地在本子上画了个圈。这个圈的意思是&#xff1a;这人背过八股文&#xff0c;但没真调过。JVM调优不是参数默写比赛&#xff0c;是诊断思维和工程判断的较…

作者头像 李华
网站建设 2026/9/27 22:59:50

RPA 与 AI Agent 的区别:为什么「非结构化输入」才是自动化的真瓶颈

RPA 的账&#xff0c;上过的团队都算过&#xff1a;确定性流程用 RPA 又便宜又稳。但真正落地一年后&#xff0c;大多数团队会撞上同一堵墙——维护成本悄悄超过开发成本&#xff1a;界面一改就要重录&#xff0c;流程稍变就得返工。问题不只在“界面脆弱”。更常见的情况是&am…

作者头像 李华