在机器学习项目的实际落地过程中,数据标注一直是制约项目效率的关键瓶颈。传统人工标注方式不仅成本高昂,而且面对大规模数据集时往往需要数周甚至数月的标注周期。Autodistill提出的自动化标注方案,通过模型提示(Model Prompting)技术,将预训练大模型的语义理解能力与轻量级视觉模型的部署效率相结合,为计算机视觉领域带来了革命性的标注效率提升。
这个方案最吸引我的地方在于其"以模型标注模型"的巧妙设计。我们不再需要从零开始训练标注模型,而是利用现成的基础模型(如CLIP、Grounding DNN等)生成伪标签,再用这些标签训练适合特定任务的小型目标检测模型。在实际测试中,这种工作流将标注效率提升了3-5倍,特别适合中小团队快速启动视觉项目。
Autodistill的自动化标注流程包含三个关键阶段:
基础模型推理阶段:
标签优化阶段:
python复制# 典型的标签优化代码示例
from autodistill import GroundingDINO
base_model = GroundingDINO(
text_prompt="car, person, traffic light",
box_threshold=0.35,
text_threshold=0.25
)
目标模型训练阶段:
这项方案的核心创新在于解决了传统自动标注的三大痛点:
语义鸿沟问题:
标注质量瓶颈:
计算成本控制:
对于大多数视觉标注任务,推荐以下配置方案:
| 组件 | 推荐规格 | 备注 |
|---|---|---|
| GPU | NVIDIA RTX 3090 | 24GB显存可处理4K图像 |
| 内存 | 32GB以上 | 处理大规模数据集时必要 |
| 基础模型 | GroundingDINO+CLIP | 文本-图像对齐效果最佳 |
| 目标模型 | YOLOv8n | 平衡速度与精度 |
安装核心依赖:
bash复制pip install autodistill autodistill-grounding-dino autodistill-yolov8
以交通场景标注为例:
准备待标注图像集
python复制from autodistill.core import Dataset
dataset = Dataset.from_folder("traffic_images/")
配置基础模型提示
python复制from autodistill_grounding_dino import GroundingDINO
base_model = GroundingDINO(
text_prompt="car, truck, pedestrian, bicycle, traffic sign",
box_threshold=0.4
)
生成伪标签
python复制labeled_data = base_model.label(
input_folder="traffic_images/",
output_folder="labeled_data/"
)
训练目标模型
python复制from autodistill_yolov8 import YOLOv8
target_model = YOLOv8("yolov8n.pt")
target_model.train("labeled_data/data.yaml", epochs=100)
模型导出与部署
python复制target_model.export(format="onnx")
在实际项目中,以下几个参数对标注质量影响最大:
1.# 1. 概述
本文,我们来分享 MyBatis 的日志模块,对应 logging 包。如下图所示:
logging 包
在 《精尽 MyBatis 源码解析 —— 项目结构一览》 中,简单介绍了这个模块如下:
无论在开发测试环境中,还是在线上生产环境中,日志在整个系统中的地位都是非常重要的。良好的日志功能可以帮助开发人员和测试人员快速定位 Bug 代码,也可以帮助运维人员快速定位性能瓶颈等问题。目前的 Java 世界中存在很多优秀的日志框架,例如 Log4j、 Log4j2、Slf4j 等。
MyBatis 作为一个设计优良的框架,除了提供详细的日志输出信息,还要能够集成多种日志框架,其日志模块的一个主要功能就是集成第三方日志框架。
本文涉及的类如下图所示:
下面,我们逐个类来瞅瞅。
org.apache.ibatis.logging.LogFactory ,Log 工厂类。
java复制// LogFactory.java
/**
* Marker to be used by logging implementations that support markers
*/
public static final String MARKER = "MYBATIS";
/**
* 使用的 Log 的构造方法
*/
private static Constructor<? extends Log> logConstructor;
static {
// <1> 逐个尝试,判断使用哪个 Log 的实现类,即初始化 logConstructor 属性
tryImplementation(LogFactory::useSlf4jLogging);
tryImplementation(LogFactory::useCommonsLogging);
tryImplementation(LogFactory::useLog4J2Logging);
tryImplementation(LogFactory::useLog4JLogging);
tryImplementation(LogFactory::useJdkLogging);
tryImplementation(LogFactory::useNoLogging);
}
<1> 处,基于 tryImplementation 方法,逐个尝试,判断使用哪个 Log 的实现类,即初始化 logConstructor 属性。代码如下:
java复制// LogFactory.java
private static void tryImplementation(Runnable runnable) {
if (logConstructor == null) {
try {
runnable.run();
} catch (Throwable t) {
// ignore
}
}
}
当 logConstructor 为空时,执行 runnable 的方法。那么,runnable 是怎么样的呢?以 #useSlf4jLogging() 方法举例子。代码如下:
java复制// LogFactory.java
public static synchronized void useSlf4jLogging() {
setImplementation(org.apache.ibatis.logging.slf4j.Slf4jImpl.class);
}
private static void setImplementation(Class<? extends Log> implClass) {
try {
// 获得参数为 String 的构造方法
Constructor<? extends Log> candidate = implClass.getConstructor(String.class);
// 创建 Log 对象
Log log = candidate.newInstance(LogFactory.class.getName());
if (log.isDebugEnabled()) {
log.debug("Logging initialized using '" + implClass + "' adapter.");
}
// 创建成功,意味着可以使用,设置为 logConstructor
logConstructor = candidate;
} catch (Throwable t) {
throw new LogException("Error setting Log implementation. Cause: " + t, t);
}
}
#useCommonsLogging()、#useLog4J2Logging() 等等方法,和这个方法逻辑一致。#getLog(...) 静态方法,获得 Log 对象。代码如下:
java复制// LogFactory.java
public static Log getLog(Class<?> aClass) {
return getLog(aClass.getName());
}
public static Log getLog(String logger) {
try {
return logConstructor.newInstance(logger);
} catch (Throwable t) {
throw new LogException("Error creating logger for logger " + logger + ". Cause: " + t, t);
}
}
#useCustomLogging(...) 静态方法,设置自定义的 Log 实现类。代码如下:
java复制// LogFactory.java
public static synchronized void useCustomLogging(Class<? extends Log> clazz) {
setImplementation(clazz);
}
org.apache.ibatis.logging.Log ,MyBatis Log 接口。代码如下:
java复制// Log.java
public interface Log {
boolean isDebugEnabled();
boolean isTraceEnabled();
void error(String s, Throwable e);
void error(String s);
void debug(String s);
void trace(String s);
void warn(String s);
}
Log 的实现类较多,但是每个实现类都相似,所以胖友可以自己简单浏览即可。如下是每个实现类对应的包:
org.apache.ibatis.logging.commons :基于 commons-logging 实现。org.apache.ibatis.logging.jdk14 :基于 jdk logging 实现。org.apache.ibatis.logging.log4j :基于 log4j 实现。org.apache.ibatis.logging.log4j2 :基于 log4j2 实现。org.apache.ibatis.logging.nologging :基于 空 实现。org.apache.ibatis.logging.slf4j :基于 slf4j 实现。org.apache.ibatis.logging.stdout :基于 System.out 实现。org.apache.ibatis.logging.BaseJdbcLogger ,实现 Log 接口,负责打印 JDBC 相关的日志。代码如下:
java复制// BaseJdbcLogger.java
public abstract class BaseJdbcLogger implements Log {
/**
* SET 方法前缀
*/
protected static final String SET_METHOD_PREFIX = "set";
/**
* EXECUTE 方法前缀
*/
private static final String EXECUTE_METHOD_PREFIX = "execute";
/**
* GET 方法前缀
*/
private static final String GET_METHOD_PREFIX = "get";
/**
* 待忽略的的方法名
*/
private static final String[] EXECUTE_METHODS = new String[]{"execute", "update", "batch"};
/**
* Connection 连接
*/
protected final Log connectionLog;
/**
* Statement 日志的当前层级
*/
protected final int connectionLogDebugLevel;
/**
* ResultSet 日志的当前层级
*/
protected final int resultSetLogDebugLevel;
/**
* Statement 日志的当前层级
*/
protected final int statementLogDebugLevel;
/**
* PreparedStatement 参数集合
*/
protected final Map<Object, Object> columnMap = new HashMap<>();
/**
* PreparedStatement 参数集合
*/
protected final List<Object> columnNames = new ArrayList<>();
/**
* PreparedStatement 参数集合
*/
protected final List<Object> columnValues = new ArrayList<>();
/**
* 最后执行的 SQL
*/
protected String lastSql;
public BaseJdbcLogger(Log log, int queryStack) {
this.connectionLog = log;
this.connectionLogDebugLevel = queryStack * 4 + 1;
this.statementLogDebugLevel = queryStack * 4 + 2;
this.resultSetLogDebugLevel = queryStack * 4 + 3;
}
}
queryStack 参数,在 ConnectionLogger 和 PreparedStatementLogger 中,都会进行递增。这样,可以更好的区分日志的层级。java复制// BaseJdbcLogger.java
protected void debug(String text, boolean input) {
if (connectionLog.isDebugEnabled()) {
connectionLog.debug(prefix(input) + text);
}
}
protected void trace(String text, boolean input) {
if (connectionLog.isTraceEnabled()) {
connectionLog.trace(prefix(input) + text);
}
}
private String prefix(boolean isInput) {
char[] buffer = new char[connectionLogDebugLevel * 2 + 2];
Arrays.fill(buffer, '=');
buffer[connectionLogDebugLevel * 2 + 1] = ' ';
if (isInput) {
buffer[connectionLogDebugLevel * 2] = '>';
} else {
buffer[0] = '<';
}
return new String(buffer);
}
通过 #prefix(...) 方法,拼接日志的前缀。例如:
java复制==> Preparing: select * from subject where id = ?
==> Parameters: 1(Integer)
<== Total: 1
java复制// BaseJdbcLogger.java
protected void setColumn(Object key, Object value) {
columnMap.put(key, value);
columnNames.add(key);
columnValues.add(value);
}
protected Object getColumn(Object key) {
return columnMap.get(key);
}
protected String getParameterValueString() {
List<Object> typeList = new ArrayList<>(columnValues.size());
// 遍历 columnValues 数组
for (Object value : columnValues) {
if (value == null) {
typeList.add("null");
} else {
typeList.add(objectValueString(value) + "(" + value.getClass().getSimpleName() + ")");
}
}
// 拼接
final String parameters = typeList.toString();
return parameters.substring(1, parameters.length() - 1);
}
protected String objectValueString(Object value) {
if (value instanceof Array) {
try {
return Arrays.toString((Object[]) ((Array) value).getArray());
} catch (SQLException e) {
return value.toString();
}
}
return value.toString();
}
protected String getColumnString() {
return columnNames.toString();
}
protected void clearColumnInfo() {
columnMap.clear();
columnNames.clear();
columnValues.clear();
}
protected String removeBreakingWhitespace(String original) {
return WHITESPACE_PATTERN.matcher(original).replaceAll(" ");
}
protected boolean isDebugEnabled() {
return connectionLog.isDebugEnabled();
}
protected boolean isTraceEnabled() {
return connectionLog.isTraceEnabled();
}
protected void debug(String text) {
debug(text, false);
}
protected void trace(String text) {
trace(text, false);
}
java复制// BaseJdbcLogger.java
protected boolean isStatementMethod(String methodName) {
return EXECUTE_METHOD_PREFIX.equals(methodName);
}
protected boolean isResultSetMethod(String methodName) {
return GET_METHOD_PREFIX.equals(methodName);
}
protected boolean isSetMethod(String methodName) {
return methodName.startsWith(SET_METHOD_PREFIX);
}
protected boolean isExecuteMethod(String methodName) {
for (String executeMethod : EXECUTE_METHODS) {
if (executeMethod.equals(methodName)) {
return true;
}
}
return false;
}
org.apache.ibatis.logging.jdbc.ConnectionLogger ,继承 BaseJdbcLogger 类,Connection 日志增强。通过这样的方式,MyBatis 可以打印 Connection 开启事务、提交事务、回滚事务、关闭的日志。
java复制// ConnectionLogger.java
/**
* Connection 对象的代理
*/
private final Connection connection;
private ConnectionLogger(Connection conn, Log statementLog, int queryStack) {
super(statementLog, queryStack);
this.connection = conn;
}
java复制// ConnectionLogger.java
public static Connection newInstance(Connection conn, Log statementLog, int queryStack) {
// 创建 InvocationHandler 对象
InvocationHandler handler = new ConnectionLogger(conn, statementLog, queryStack);
ClassLoader cl = Connection.class.getClassLoader();
// 创建 Connection 代理
return (Connection) Proxy.newProxyInstance(cl, new Class[]{Connection.class}, handler);
}
java复制// ConnectionLogger.java
@Override
public Object invoke(Object proxy, Method method, Object[] params)
throws Throwable {
try {
// 如果调用的是 Object 定义的方法,直接调用,不进行代理
if (Object.class.equals(method.getDeclaringClass())) {
return method.invoke(this, params);
}
// 准备执行方法
String methodName = method.getName();
// 调用提交事务
if ("commit".equals(methodName)) {
// 打印日志
if (isDebugEnabled()) {
debug("Committing JDBC Connection [" + connection + "]");
}
// 执行 commit 方法
return method.invoke(connection, params);
// 调用回滚事务
} else if ("rollback".equals(methodName)) {
// 打印日志
if (isDebugEnabled()) {
debug("Rolling back JDBC Connection [" + connection + "]");
}
// 执行 rollback 方法
return method.invoke(connection, params);
// 调用关闭 Connection 连接
} else if ("close".equals(methodName)) {
// 打印日志
if (isDebugEnabled()) {
debug("Closing JDBC Connection [" + connection + "]");
}
// 执行 close 方法
return method.invoke(connection, params);
// 创建 Statement 或 PrepareStatement 对象
} else if ("createStatement".equals(methodName)) {
// 执行 createStatement 方法
Statement stmt = (Statement) method.invoke(connection, params);
// 创建 Statement 的代理对象
stmt = StatementLogger.newInstance(stmt, statementLog, queryStack);
// 打印日志
if (isDebugEnabled()) {
debug("Created statement " + stmt);
}
return stmt;
} else if ("prepareStatement".equals(methodName)) {
// 执行 prepareStatement 方法
PreparedStatement stmt = (PreparedStatement) method.invoke(connection, params);
// 创建 PreparedStatement 的代理对象
stmt = PreparedStatementLogger.newInstance(stmt, statementLog, queryStack);
// 打印日志
if (isDebugEnabled()) {
debug("Preparing statement " + stmt);
}
return stmt;
// 创建 CallableStatement 对象
} else if ("prepareCall".equals(methodName)) {
// 执行 prepareCall 方法
CallableStatement stmt = (CallableStatement) method.invoke(connection, params);
// 创建 CallableStatement 的代理对象
stmt = CallableStatementLogger.newInstance(stmt, statementLog, queryStack);
// 打印日志
if (isDebugEnabled()) {
debug("Preparing callable statement " + stmt);
}
return stmt;
} else {
return method.invoke(connection, params);
}
} catch (Throwable t) {
throw ExceptionUtil.unwrapThrowable(t);
}
}
#createStatement()、#prepareStatement()、#prepareCall() 方法,会创建对应的 StatementLogger、PreparedStatementLogger、CallableStatementLogger 代理对象。这样,后续 Statement 的执行,也会打印相应的日志。org.apache.ibatis.logging.jdbc.StatementLogger ,继承 BaseJdbcLogger 类,Statement 日志增强。
java复制// StatementLogger.java
/**
* Statement 对象的代理
*/
private final Statement statement;
private StatementLogger(Statement stmt, Log statementLog, int queryStack) {
super(statementLog, queryStack);
this.statement = stmt;
}
java复制// StatementLogger.java
public static Statement newInstance(Statement stmt, Log statementLog, int queryStack) {
// 创建 InvocationHandler 对象
InvocationHandler handler = new StatementLogger(stmt, statementLog, queryStack);
ClassLoader cl = Statement.class.getClassLoader();
// 创建 Statement 代理对象
return (Statement) Proxy.newProxyInstance(cl, new Class[]{Statement.class}, handler);
}
java复制// StatementLogger.java
@Override
public Object invoke(Object proxy, Method method, Object[] params) throws Throwable {
try {
// 如果调用的是 Object 定义的方法,直接调用,不进行代理
if (Object.class.equals(method.getDeclaringClass())) {
return method.invoke(this, params);
}
// 执行的方法
String methodName = method.getName();
// 如果是调用 SQL 的方法
if (EXECUTE_METHODS.contains(methodName)) {
// 打印日志
if (isDebugEnabled()) {
debug("Executing: " + removeBreakingWhitespace((String) params[0]), true);
}
// 执行方法
Object result = method.invoke(statement, params);
// 打印日志
if (result instanceof ResultSet) {
ResultSet rs = (ResultSet) result;
rs = ResultSetLogger.newInstance(rs, statementLog, queryStack);
}
return result;
} else if ("getResultSet".equals(methodName)) {
// 执行方法
Object result = method.invoke(statement, params);
// 打印日志
if (result instanceof ResultSet) {
ResultSet rs = (ResultSet) result;
rs = ResultSetLogger.newInstance(rs, statementLog, queryStack);
}
return result;
} else {
return method.invoke(statement, params);
}
} catch (Throwable t) {
throw ExceptionUtil.unwrapThrowable(t);
}
}
EXECUTE_METHODS 和 "getResultSet" 方法,会创建对应的 ResultSetLogger 代理对象。这样,后续 ResultSet 的遍历,也会打印相应的日志。org.apache.ibatis.logging.jdbc.PreparedStatementLogger ,继承 BaseJdbcLogger 类,PreparedStatement 日志增强。
java复制// PreparedStatementLogger.java
/**
* PreparedStatement 对象的代理
*/
private final PreparedStatement statement;
private PreparedStatementLogger(PreparedStatement stmt, Log statementLog, int queryStack) {
super(statementLog, queryStack);
this.statement = stmt;
}
java复制// PreparedStatementLogger.java
public static PreparedStatement newInstance(PreparedStatement stmt, Log statementLog, int queryStack) {
// 创建 InvocationHandler 对象
InvocationHandler handler = new PreparedStatementLogger(stmt, statementLog, queryStack);
ClassLoader cl = PreparedStatement.class.getClassLoader();
// 创建 PreparedStatement 代理对象
return (PreparedStatement) Proxy.newProxyInstance(cl, new Class[]{PreparedStatement.class, CallableStatement.class}, handler);
}
java复制// PreparedStatementLogger.java
@Override
public Object invoke(Object proxy, Method method, Object[] params) throws Throwable {
try {
// 如果调用的是 Object 定义的方法,直接调用,不进行代理
if (Object.class.equals(method.getDeclaringClass())) {
return method.invoke(this, params);
}
// 执行的方法
String methodName = method.getName();
// 如果是调用 SQL 的方法
if (EXECUTE_METHODS.contains(methodName)) {
// 打印日志
if (isDebugEnabled()) {
debug("Parameters: " + getParameterValueString(), true);
}
// 清除参数
clearColumnInfo();
// 执行方法
Object result = method.invoke(statement, params);
// 打印日志
if (result instanceof ResultSet) {
ResultSet rs = (ResultSet) result;
rs = ResultSetLogger.newInstance(rs, statementLog, queryStack);
}
return result;
} else if (SET_METHODS.contains(methodName)) {
// 如果是设置 SQL 参数的方法
if ("setNull".equals(methodName)) {
setColumn(params[0], null);
} else {
setColumn(params[0], params[1]);
}
// 执行方法
return method.invoke(statement, params);
} else if ("getResultSet".equals(methodName)) {
// 执行方法
Object result = method.invoke(statement, params);
// 打印日志
if (result instanceof ResultSet) {
ResultSet rs = (ResultSet) result;
rs = ResultSetLogger.newInstance(rs, statementLog, queryStack);
}
return result;
} else if ("getUpdateCount".equals(methodName)) {
// 执行方法
Object result = method.invoke(statement, params);
// 打印日志
if (result instanceof Integer && ((Integer) result) > 0) {
debug(" Updates: " + result, false);
}
return result;
} else {
return method.invoke(statement, params);
}
} catch (Throwable t) {
throw ExceptionUtil.unwrapThrowable(t);
}
}
SET_METHODS 方法,会将设置 SQL 参数,记录到 columnMap、columnNames、columnValues 中。这样,后续打印的 Parameters 日志,就是从这三个集合中获取的。org.apache.ibatis.logging.jdbc.ResultSetLogger ,继承 BaseJdbcLogger 类,ResultSet 日志增强。
java复制// ResultSetLogger.java
/**
* ResultSet 对象的代理
*/
private final ResultSet rs;
/**
* 读取的列数
*/
private int rows;
/**
* 第一行,就进行日志打印。否则,不打印
*/
private final boolean first = true;
private ResultSetLogger(ResultSet rs, Log statementLog, int queryStack) {
super(statementLog, queryStack);
this.rs = rs;
}
java复制// ResultSetLogger.java
public static ResultSet newInstance(ResultSet rs, Log statementLog, int queryStack) {
// 创建 InvocationHandler 对象
InvocationHandler handler = new ResultSetLogger(rs, statementLog, queryStack);
ClassLoader cl = ResultSet.class.getClassLoader();
// 创建 ResultSet 代理对象
return (ResultSet) Proxy.newProxyInstance(cl, new Class[]{ResultSet.class}, handler);
}
java复制// ResultSetLogger.java
@Override
public Object invoke(Object proxy, Method method, Object[] params) throws Throwable {
try {
// 如果调用的是 Object 定义的方法,直接调用,不进行代理
if (Object.class.equals(method.getDeclaringClass())) {
return method.invoke(this, params);
}
// 执行的方法
Object o = method.invoke(rs, params);
// 如果是调用 next 方法
if ("next".equals(method.getName())) {
// 返回 true ,说明有记录
if ((Boolean) o) {
// 增加行数
rows++;
// 是否打印日志
if (isTraceEnabled()) {
// 获得 ResultSetMetaData 对象
ResultSetMetaData rsmd = rs.getMetaData();
// 获得字段的个数
final int columnCount = rsmd.getColumnCount();
// 如果是第一行,则打印表头
if (first) {
first = false;
// 打印列名
printColumnHeaders(rsmd, columnCount);
}
// 打印列值
printColumnValues(columnCount);
}
} else { // 返回 false ,说明无记录,打印总行数
debug(" Total: " + rows, false);
}
}
// 清空第一行的标记
clearColumnInfo();
return o;
} catch (Throwable t) {
throw ExceptionUtil.unwrapThrowable(t);
}
}
#next() 方法,会打印 ResultSet 的日志。比较有意思的是,first 属性,可以避免所有的 ResultSet 都打印日志,而是只打印首条。java复制// ResultSetLogger.java
private void printColumnHeaders(ResultSetMetaData rsmd, int columnCount) throws SQLException {
StringBuilder row = new StringBuilder();
row.append(" Columns: ");
for (int i = 1; i <= columnCount; i++) {
if (i > 1) {
row.append(", ");
}
row.append(rsmd.getColumnLabel(i));
}
trace(row.toString(), false);
}
java复制// ResultSetLogger.java
private void printColumnValues(int columnCount) {
StringBuilder row = new StringBuilder();
row.append(" Row: ");
for (int i = 1; i <= columnCount; i++) {
if (i > 1) {
row.append(", ");
}
try {
String colval;
try {
colval = rs.getString(i);
} catch (SQLException e) {
// generally can't call getString() on a BLOB column
colval = "<Cannot display value>";
}
row.append(colval);
} catch (SQLException e) {
row.append("<Cannot display value>");
}
}
trace(row.toString(), false);
}
我们以 PreparedStatementLogger 举例子。在 PreparedStatementLogger#invoke(...) 方法中,会调用 #debug(String text, boolean input) 方法,打印日志。代码如下:
java复制// BaseJdbcLogger.java
protected void debug(String text, boolean input) {
if (connectionLog.isDebugEnabled()) {
connectionLog.debug(prefix(input) + text);
}
}
其中,connectionLog 属性,就是我们在构造方法中传入的 Log 对象。以 Slf4jImpl 举例子,则最终会调用 Slf4jLogger#debug(String s) 方法,代码如下:
java复制// Slf4jLogger.java
@Override
public void debug(String s) {
logger.debug(s);
}
org.slf4j.Logger 的方法,打印日志。在 org.apache.ibatis.logging.jdbc 包下,还有 BaseJdbcLogger 的三个子类,用于打印 JDBC 调试的日志。整体代码比较简单,感兴趣的胖友,自己简单看看即可。
ConnectionLoggerStatementLoggerPreparedStatementLoggerResultSetLogger