简谈源码-Picasso(v2.71828)

本文深入剖析了Picasso图片加载库的实现原理,包括单例模式、建造者模式的应用,OkHttp3网络请求,LRU缓存算法,Handler线程切换等关键技术点。详细介绍了Picasso的初始化、图片加载、显示流程,以及内部组件如下载器、缓存器、线程池、分发器的工作机制。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

概述:设计模式用到单例和建造者;网络请求使用OkHttp3;缓存算法使用LRU;线程切换使用Handler

Picasso官网
Picasso

Picasso.get().load(url).into(iv);

Picasso的常见使用步骤很简单,下面我们概要的看看其实现。

1. 实例获取get()

/* Picasso.class */
@SuppressLint("StaticFieldLeak") static volatile Picasso singleton = null;

public static Picasso get() {
    if (singleton == null) {
        synchronized (Picasso.class) {
            if (singleton == null) {
                if (PicassoProvider.context == null) {
                    throw new IllegalStateException("context == null");
                }
                singleton = new Builder(PicassoProvider.context).build();
            }
        }
    }
    return singleton;
}

使用double checked locking单例模式创建Picasso实例,创建过程中使用建造者Builder。

1.1 context获取

通过ContentProvider,在AndroidManifest 中注册PicassoProvider,app启动后调用onCreate()即可获取所需context实例。

public final class PicassoProvider extends ContentProvider {

    @SuppressLint("StaticFieldLeak") static Context context;

    @Override public boolean onCreate() {
        context = getContext();
        return true;
    }
}

1.2 Picasso创建

使用Builder Pattern建造者模式,将一个复杂的构建与其表示相分离,使得同样的构建过程可以创建不同的表示,简单点说就是用多个简单的对象一步一步构建成一个复杂的对象。

/* Picasso.class */
public Builder(@NonNull Context context) {
    if (context == null) {
        throw new IllegalArgumentException("Context must not be null.");
    }
    this.context = context.getApplicationContext();
}

具体建造过程如下。

/* Picasso.class */
public Picasso build() {
    Context context = this.context;

    // 配置下载器,下载网络图片资源(默认使用OkHttp3Downloader)
    if (downloader == null) {
        downloader = new OkHttp3Downloader(context);
    }

    // 配置内存缓存,提高图片加载效率(默认使用LruCache[Least Recently Used])
    // 内存缓存大小为RAM的15%;磁盘缓存大小为ROM的2%且范围为5~50mb
    if (cache == null) {
        cache = new LruCache(context);
    }

    // 配置线程池
    // 默认3个线程;使用wifi为4个,4G为3个,3G为2个,2G为1个
    if (service == null) {
        service = new PicassoExecutorService();
    }

    // 配置请求转换器(默认不做处理,直接返回原请求)
    if (transformer == null) {
        transformer = RequestTransformer.IDENTITY;
    }

    // 统计:统计缓存命中数量、图片下载数量等
    Stats stats = new Stats(cache);

    // 分发器:由HANDLER分发图片请求、请求完成等等事件
    Dispatcher dispatcher = new Dispatcher(context, service, HANDLER, downloader, cache, stats);

    return new Picasso(context, dispatcher, cache, listener, transformer, requestHandlers, stats,
            defaultBitmapConfig, indicatorsEnabled, loggingEnabled);
}

至此,Picasso.get()过程完成。

2. 获取请求load(url)

即通过请求创建器RequestCreator创建请求Request,通过Request可以执行placeholder(placeholderResId)方法设置占位图,error(errorResId)方法设置错误图,noFade()方法取消图片渐变效果等操作。

/* Picasso.class */
public RequestCreator load(@Nullable String path) {
    ...
    return load(Uri.parse(path));
}

public RequestCreator load(@Nullable Uri uri) {
    return new RequestCreator(this, uri, 0);
}

RequestCreator的构造方法如下,核心是通过建造者模式建造Request实例。

通过picasso可以发起请求、推迟(defer)请求、取消请求、检查缓存配置等操作;通过创建的Request实例data可以调用centerCrop()、centerInside()等方法。

boolean shutdown;
private final Picasso picasso;
private final Request.Builder data;

RequestCreator(Picasso picasso, Uri uri, int resourceId) {
    if (picasso.shutdown) {
        throw new IllegalStateException("Picasso instance already shut down. Cannot submit new requests.");
    }
    this.picasso = picasso;
    this.data = new Request.Builder(uri, resourceId, picasso.defaultBitmapConfig);
}

3. 显示into(iv)

核心逻辑为创建Action,即一次图片请求的活动过程,然后分发该action。

/* RequestCreator.class */
/**
 * 异步请求图片资源至用户指定的ImageView
 * 使用弱引用关联ImageView,资源将会被自动回收
 */
public void into(ImageView target) {
    into(target, null);
}

/**
 * 若用户指定Callback,为了防止Activity/Fragment回收,会是一个强引用;推荐及时调用cancelRequest()方法防止内存泄露
 */
public void into(ImageView target, Callback callback) {
    // 记录开始时间,并检查线程是否安全
    long started = System.nanoTime();
    checkMain();

    if (target == null) {
        throw new IllegalArgumentException("Target must not be null.");
    }

    // 若Request实例为空,取消当前请求,设置占位图
    if (!data.hasImage()) {
        picasso.cancelRequest(target);
        if (setPlaceholder) {
            setPlaceholder(target, getPlaceholderDrawable());
        }
        return;
    }

    // deferred默认是false的
    if (deferred) {
        if (data.hasSize()) {
            throw new IllegalStateException("Fit cannot be used with resize.");
        }
        int width = target.getWidth();
        int height = target.getHeight();
        if (width == 0 || height == 0) {
            if (setPlaceholder) {
                setPlaceholder(target, getPlaceholderDrawable());
            }
            // 若获取不到目标ImageView的宽高,就延迟请求,直到获取宽高时再次调用into()方法
            picasso.defer(target, new DeferredRequestCreator(this, target, callback));
            return;
        }
        data.resize(width, height);
    }

    // 创建Request实例并标记
    Request request = createRequest(started);
    String requestKey = createKey(request);

    // 控制从缓存中读取,若命中requestKey对应的Bitmap则取消当前请求,并显示图片
    if (shouldReadFromMemoryCache(memoryPolicy)) {
        Bitmap bitmap = picasso.quickMemoryCacheCheck(requestKey);
        if (bitmap != null) {
            picasso.cancelRequest(target);
            setBitmap(target, picasso.context, bitmap, MEMORY, noFade, picasso.indicatorsEnabled);
            if (picasso.loggingEnabled) {
                log(OWNER_MAIN, VERB_COMPLETED, request.plainId(), "from " + MEMORY);
            }
            if (callback != null) {
                callback.onSuccess();
            }
            return;
        }
    }

    if (setPlaceholder) {
        setPlaceholder(target, getPlaceholderDrawable());
    }

    // 内存缓存未命中,则创建Action实例,并提交至picasso
    Action action =
            new ImageViewAction(picasso, target, request, memoryPolicy, networkPolicy, errorResId,
                    errorDrawable, requestKey, tag, callback, noFade);

    picasso.enqueueAndSubmit(action);
}

3.1 action的创建

上述代码中用到的关键活动类ImageViewAction就是继承自Action,并重写了Action中的方法complete()、error()、cancel(),具体实现和方法注释说明如下。

class ImageViewAction extends Action<ImageView> {

    Callback callback;

    ImageViewAction(Picasso picasso, ImageView imageView, Request data, int memoryPolicy,
                    int networkPolicy, int errorResId, Drawable errorDrawable, String key, Object tag,
                    Callback callback, boolean noFade) {
        super(picasso, imageView, data, memoryPolicy, networkPolicy, errorResId, errorDrawable, key,
                tag, noFade);
        this.callback = callback;
    }

    // 图片请求成功,使用PicassoDrawable将图片显示到指定的ImageView控件中
    @Override public void complete(Bitmap result, Picasso.LoadedFrom from) {
        if (result == null) {
            throw new AssertionError(
                    String.format("Attempted to complete action with no result!\n%s", this));
        }

        ImageView target = this.target.get();
        if (target == null) {
            return;
        }

        Context context = picasso.context;
        boolean indicatorsEnabled = picasso.indicatorsEnabled;
        PicassoDrawable.setBitmap(target, context, result, from, noFade, indicatorsEnabled);

        if (callback != null) {
            callback.onSuccess();
        }
    }

    // 图片请求失败,判断是否显示占位图
    @Override public void error(Exception e) {
        ImageView target = this.target.get();
        if (target == null) {
            return;
        }
        Drawable placeholder = target.getDrawable();
        if (placeholder instanceof Animatable) {
            ((Animatable) placeholder).stop();
        }
        if (errorResId != 0) {
            target.setImageResource(errorResId);
        } else if (errorDrawable != null) {
            target.setImageDrawable(errorDrawable);
        }

        if (callback != null) {
            callback.onError(e);
        }
    }

    // 取消本次请求
    @Override void cancel() {
        super.cancel();
        if (callback != null) {
            callback = null;
        }
    }
}

3.2 action的提交

picasso提交action,然后通过分发器dispatcher分发消息。

/* Picasso.class */
void enqueueAndSubmit(Action action) {
    ...
    submit(action);
}

void submit(Action action) {
    dispatcher.dispatchSubmit(action);
}

3.3 action的分发

dispatcher通过Handler发送action,交由图片捕获器BitmapHunter处理。

/* Dispatcher.class */
void dispatchSubmit(Action action) {
    handler.sendMessage(handler.obtainMessage(REQUEST_SUBMIT, action));
}

@Override public void handleMessage(final Message msg) {
    switch (msg.what) {
        case REQUEST_SUBMIT: {
            Action action = (Action) msg.obj;
            dispatcher.performSubmit(action);
            break;
        ...
        }
    }
}

void performSubmit(Action action) {
    performSubmit(action, true);
}

void performSubmit(Action action, boolean dismissFailed) {
    ...
    // 剩下的工作将由BitmapHunter完成
    BitmapHunter hunter = BitmapHunter.forRequest(action.getPicasso(), this, cache, stats, action);
    hunter.future = service.submit(hunter);
    ...
}

3.3.1 BitmapHunter.forRequest()获取图片捕获器

/* BitmapHunter.class */
static BitmapHunter forRequest(Picasso picasso, Dispatcher dispatcher, Cache cache, Stats stats, Action action) {
    Request request = action.getRequest();
    List<RequestHandler> requestHandlers = picasso.getRequestHandlers();

    for (int i = 0, count = requestHandlers.size(); i < count; i++) {
        RequestHandler requestHandler = requestHandlers.get(i);
        // 轮询获取能够处理这次活动请求(action's request)的请求处理器
        if (requestHandler.canHandleRequest(request)) {
            return new BitmapHunter(picasso, dispatcher, cache, stats, action, requestHandler);
        }
    }

    return new BitmapHunter(picasso, dispatcher, cache, stats, action, ERRORING_HANDLER);
}

其中所有的请求处理器requestHandlers是在Picasso构造时初始化的,总共为7种。

Picasso(...) {
    ...
    allRequestHandlers.add(new ResourceRequestHandler(context));
    allRequestHandlers.add(new ContactsPhotoRequestHandler(context));
    allRequestHandlers.add(new MediaStoreRequestHandler(context));
    allRequestHandlers.add(new ContentStreamRequestHandler(context));
    allRequestHandlers.add(new AssetRequestHandler(context));
    allRequestHandlers.add(new FileRequestHandler(context));
    // 加载网络图片的请求处理器
    allRequestHandlers.add(new NetworkRequestHandler(dispatcher.downloader, stats));
    ...
}

看看网络请求处理器中的匹配规则,即Uri中包含"http"或"https"。

/* NetworkRequestHandler.class */
@Override public boolean canHandleRequest(Request data) {
    String scheme = data.uri.getScheme();
    return ("http".equals(scheme) || "https".equals(scheme));
}

3.3.2 service.submit(hunter)开启图片捕获器hunter线程,获取bitmap

图片捕获器hunter是一个Runnable,hunter获取后执行submit()方法开启hunter线程。

/* ExecutorService.class */
public Future<?> submit(Runnable task) {
    ...
    execute(ftask);
    return ftask;
}

hunter线程中的核心操作就是获取图片资源,然后分发获取结果

/* BitmapHunter.class */
@Override public void run() {

    Bitmap result = hunt();

    if (result == null) {
        dispatcher.dispatchFailed(this);
    } else {
        dispatcher.dispatchComplete(this);
    }
    ...
}

Bitmap hunt() throws IOException {
    Bitmap bitmap = null;
    // 从缓存中读取图片资源
    if (shouldReadFromMemoryCache(memoryPolicy)) {
        bitmap = cache.get(key);
        ...
        return bitmap;
    }

    // 缓存未命中,使用请求处理器加载图片资源
    RequestHandler.Result result = requestHandler.load(data, networkPolicy);
    if (result != null) {
        ...
        bitmap = result.getBitmap();
        ...
    }
    ...
    return bitmap;
}

本篇以请求网络图片为背景展开介绍,load()方法中使用下载器downloader来下载图片,当前Picasso版本中的网络下载器貌似只有OkHttp3Downloader,没有UrlConnectionDownloader了。

网络请求处理器NetworkRequestHandler中的load()方法如下,即使用下载器下载图片资源。

/* NetworkRequestHandler.class */
@Override public Result load(Request request, int networkPolicy) throws IOException {
    okhttp3.Request downloaderRequest = createRequest(request, networkPolicy);
    Response response = downloader.load(downloaderRequest);
    ResponseBody body = response.body();
    ...
    return new Result(body.source(), loadedFrom);
}

网络下载器OkHttp3Downloader中的load()方法如下,请求完成即可获取图片资源。

/* OkHttp3Downloader.class */
@NonNull @Override public Response load(@NonNull Request request) throws IOException {
    return client.newCall(request).execute();
}

至此图片资源获取bitmap完成,hunter执行dispatcher.dispatchComplete(this);方法来将图片获取结果分发出去,最终图片会显示至ImageView控件,下面看看这个分发过程。

3.3.3 bitmap获取后,消息分发

/* Dispatcher.class */
void dispatchComplete(BitmapHunter hunter) {
    handler.sendMessage(handler.obtainMessage(HUNTER_COMPLETE, hunter));
}
/* Dispatcher.class */
case HUNTER_COMPLETE: {
    BitmapHunter hunter = (BitmapHunter) msg.obj;
    dispatcher.performComplete(hunter);
    break;
}
/* Dispatcher.class */
void performComplete(BitmapHunter hunter) {
    ...
    batch(hunter);
    ...
}
/* Dispatcher.class */
private void batch(BitmapHunter hunter) {
    ...
    batch.add(hunter);
    if (!handler.hasMessages(HUNTER_DELAY_NEXT_BATCH)) {
        handler.sendEmptyMessageDelayed(HUNTER_DELAY_NEXT_BATCH, BATCH_DELAY);
    }
}
/* Dispatcher.class */
case HUNTER_DELAY_NEXT_BATCH: {
    dispatcher.performBatchComplete();
    break;
}
/* Dispatcher.class */
void performBatchComplete() {
    List<BitmapHunter> copy = new ArrayList<>(batch);
    batch.clear();
    mainThreadHandler.sendMessage(mainThreadHandler.obtainMessage(HUNTER_BATCH_COMPLETE, copy));
    logBatch(copy);
}
/* Picasso.class */
case HUNTER_BATCH_COMPLETE: {
    @SuppressWarnings("unchecked") List<BitmapHunter> batch = (List<BitmapHunter>) msg.obj;
    for (int i = 0, n = batch.size(); i < n; i++) {
        BitmapHunter hunter = batch.get(i);
        hunter.picasso.complete(hunter);
    }
    break;
}
/* Picasso.class */
void complete(BitmapHunter hunter) {
    Action single = hunter.getAction();
    ...
    Exception exception = hunter.getException();
    Bitmap result = hunter.getResult();
    LoadedFrom from = hunter.getLoadedFrom();

    if (single != null) {
        deliverAction(result, from, single, exception);
    }
    ...
}
/* Picasso.class */
private void deliverAction(Bitmap result, LoadedFrom from, Action action, Exception e) {
    ...
    action.complete(result, from);
    ...
}

最后,本次action资源请求成功,调用其complete()方法,通过PicassoDrawable的setBitmap()方法来将图片显示至目标控件中。

/* ImageViewAction.class */
@Override public void complete(Bitmap result, Picasso.LoadedFrom from) {
    ...
    PicassoDrawable.setBitmap(target, context, result, from, noFade, indicatorsEnabled);
    ...
}

PicassoDrawable的setBitmap()方法中,将获取到的bitmap转为drawable,然后设置进ImageView控件中,即可完成图片的显示。

/* PicassoDrawable.class */
static void setBitmap(ImageView target, Context context, Bitmap bitmap, Picasso.LoadedFrom loadedFrom, boolean noFade, boolean debugging) {
    ...
    PicassoDrawable drawable = new PicassoDrawable(context, bitmap, placeholder, loadedFrom, noFade, debugging);
    target.setImageDrawable(drawable);
}

小结:上述流程即先在get()方法中创建Picasso,其中包含下载器downloader、缓存器cache、线程池service、请求转换器transformer、分发器dispatcher。然后在load(url)方法中创建请求Request实例,通过请求实例可以设置占位图,调整图片显示尺寸等操作。紧接着在into(iv)方法中创建一个请求图片的活动action,action被提交给了图片捕获器hunter线程,hunter启动后,先为action匹配合适的请求处理器requestHandler,然后requestHandler使用下载器downloader从网络下载图片资源,网络请求完成后,通过分发器dispatcher告知action本次图片资源获取成功。最后在action的complete()方法中使用PicassoDrawable将图片设置进目标控件,完成图片的显示。


4. 额外说明

下面对Picasso对象的各个核心内部对象简要的看一下。

4.1 下载器downloader

当前Picasso版本的下载器只有OkHttp3Downloader。

4.2. 缓存器cache

使用的是LruCache,具体的内存和磁盘缓存空间为RAM的15%和磁盘的2%且范围为5~50mb。

memory cache of 15% the available application RAM,Disk cache of 2% storage space up to 50MB but no less than 5MB。

4.3. 线程池service

默认池数为3,网络状态为wifi时为4,4G时为3,3G时为2,2G时为1。

private static final int DEFAULT_THREAD_COUNT = 3;

switch (info.getType()) {
    case ConnectivityManager.TYPE_WIFI:
    case ConnectivityManager.TYPE_WIMAX:
    case ConnectivityManager.TYPE_ETHERNET:
        setThreadCount(4);
        break;
    case ConnectivityManager.TYPE_MOBILE:
        switch (info.getSubtype()) {
            case TelephonyManager.NETWORK_TYPE_LTE:  // 4G
                setThreadCount(3);
                break;
            case TelephonyManager.NETWORK_TYPE_UMTS: // 3G
                setThreadCount(2);
                break;
            case TelephonyManager.NETWORK_TYPE_GPRS: // 2G
                setThreadCount(1);
                break;
        }
        break;
}

4.4. 请求转换器transformer

默认为不做任何转换处理,直接返回原请求。

RequestTransformer IDENTITY = new RequestTransformer() {
    @Override public Request transformRequest(Request request) {
        return request;
    }
};

4.5. 分发器dispatcher

主要就是通过Handler来分发各种消息。

其中P PP为真实值,Q QQ为预测值。2、计算交叉熵的步骤:1)步骤说明:①将predict_scores进行softmax运算,将运算结果记为pred_scores_soft;②将pred_scores_soft进行log运算,将运算结果记为pred_scores_soft_log;③将pred_scores_soft_log与真实值进行计算处理。思路即:s c o r e s → s o f t m a x → l o g → c o m p u t e scores\to softmax\to log\to computescores→softmax→log→compute2)举一个例子对计算进行说明:P 1 = [ 1 0 0 0 0 ] P_1=[10000][10000]P 1​ =[ 1​ 0​ 0​ 0​ 0​ ]Q 1 = [ 0.4 0.3 0.05 0.05 0.2 ] Q_1=[0.40.30.050.050.2][0.40.30.050.050.2]Q 1​ =[ 0.4​ 0.3​ 0.05​ 0.05​ 0.2​ ]H ( p , q ) = − ∑ i P ( i ) log ⁡ Q ( i ) = − ( 1 ∗ l o g 0.4 + 0 ∗ l o g 0.3 + 0 ∗ l o g 0.05 + 0 ∗ l o g 0.05 + 0 ∗ l o g 0.2 ) = − l o g 0.4 ≈ 0.916 H\left( {p,q} \right) = - \sum\limits_i {P\left( i \right)\log Q\left( i \right)}=-(1*log0.4+0*log0.3+0*log0.05+0*log0.05+0*log0.2) \\=-log0.4 \approx 0.916H(p,q)=− i∑​ P(i)logQ(i)=−(1∗log0.4+0∗log0.3+0∗log0.05+0∗log0.05+0∗log0.2)=−log0.4≈0.916如果Q 2 = [ 0.98 0.01 0 0 0.01 ] Q_2=[0.980.01000.01][0.980.01000.01]Q 2​ =[ 0.98​ 0.01​ 0​ 0​ 0.01​ ]则H ( p , q ) = − ∑ i P ( i ) log ⁡ Q ( i ) = − ( 1 ∗ l o g 0.98 + 0 ∗ l o g 0.01 + 0 ∗ l o g 0.05 + 0 ∗ l o g 0 + 0 ∗ l o g 0.01 ) = − l o g 0.98 ≈ 0.02 H\left( {p,q} \right) = - \sum\limits_i {P\left( i \right)\log Q\left( i \right)}=-(1*log0.98+0*log0.01+0*log0.05+0*log0+0*log0.01) \\=-log0.98 \approx 0.02H(p,q)=− i∑​ P(i)logQ(i)=−(1∗log0.98+0∗log0.01+0∗log0.05+0∗log0+0∗log0.01)=−log0.98≈0.02由H ( p , q ) H(p,q)H(p,q)的计算结果和直观地观察Q 1 Q_1Q 1​ 和Q 2 Q_2Q 2​ 与P 1 P_1P 1​ 的相似度,均可看出Q 2 Q_2Q 2​ 比Q 1 Q_1Q 1​ 更近似于P 1 P_1P 1​ 。二、官方文档的说明在PyTorch的官方中文文档中F.cross_entropy()的记录如下:torch.nn.functional.cross_entropy(input, target, weight=None, size_average=True)1该函数使用了 log_softmax 和 nll_loss,详细请看CrossEntropyLoss常用参数:参数名 shape 注input (N,C) C是类别的个数target N 0 <= targets[i] <= C-1三、自己的困惑在官方文档说明中,对于target参数的说明为,torch.shape为torch.Size([N]),0 <= targets[i] <= C-1。我的困惑是:网络计算输出并送入函数中的input的torch.shape为torch.Size([N,C]),它的torch.shape并不会因为softmax和log的操作而改变,但是target的torch.shape为torch.Size([N]),是一个标量而不是一个矩阵,那么如何按照上面的例子中的运算方法进行交叉熵的计算?例如:import torchimport torch.nn.functional as Fpred_score = torch.tensor([[13., 3., 2., 5., 1.], [1., 8., 20., 2., 3.], [1., 14., 3., 5., 3.]])print(pred_score)pred_score_soft = F.softmax(pred_score, dim=1)print(pred_score_soft)pred_score_soft_log = pred_score_soft.log()print(pred_score_soft_log)1234567891011它的结果为:tensor([[13., 3., 2., 5., 1.], [ 1., 8., 20., 2., 3.], [ 1., 14., 3., 5., 3.]])tensor([[9.9960e-01, 4.5382e-05, 1.6695e-05, 3.3533e-04, 6.1417e-06], [5.6028e-09, 6.1442e-06, 9.9999e-01, 1.5230e-08, 4.1399e-08], [2.2600e-06, 9.9984e-01, 1.6699e-05, 1.2339e-04, 1.6699e-05]])tensor([[-4.0366e-04, -1.0000e+01, -1.1000e+01, -8.0004e+00, -1.2000e+01], [-1.9000e+01, -1.2000e+01, -6.1989e-06, -1.8000e+01, -1.7000e+01], [-1.3000e+01, -1.5904e-04, -1.1000e+01, -9.0002e+00, -1.1000e+01]])123456789如何与一个标量target进行计算?四、分析F.Cross_entropy(input, target)函数中包含了s o f t m a x softmaxsoftmax和l o g loglog的操作,即网络计算送入的input参数不需要进行这两个操作。例如在分类问题中,input表示为一个torch.Size([N, C])的矩阵,其中,N NN为样本的个数,C CC是类别的个数,input[i][j]可以理解为第i ii个样本的类别为j jj的Scores,Scores值越大,类别为j jj的可能性越高,就像在代码块中所体现的那样。同时,一般我们将分类问题的结果作为lable表示时使用one-hot embedding,例如在手写数字识别的分类问题中,数字0的表示为[ 1 0 0 0 0 0 0 0 0 0 ][1000000000][1000000000][ 1​ 0​ 0​ 0​ 0​ 0​ 0​ 0​ 0​ 0​ ]数字3的表示为[ 0 0 0 1 0 0 0 0 0 0 ][0001000000][0001000000][ 0​ 0​ 0​ 1​ 0​ 0​ 0​ 0​ 0​ 0​ ]在手写数字识别的问题中,我们计算l o s s lossloss的方法为l o s s = ( y − y ^ ) 2 loss=(y-\hat y)^2loss=(y− y^​ ) 2 ,即求y yy的embedding的矩阵减去pred_probability矩阵的结果矩阵的范数。但是在这里,交叉熵的计算公式为H ( p , q ) = − ∑ i P ( i ) log ⁡ Q ( i ) H\left( {p,q} \right) = - \sum\limits_i {P\left( i \right)\log Q\left( i \right)}H(p,q)=− i∑​ P(i)logQ(i)其中P PP为真实值概率矩阵,Q QQ为预测值概率矩阵。那么如果P PP使用one-hot embedding的话,只有在i ii为正确分类时P ( i ) P(i)P(i)才等于1 11,否则,P ( i ) P(i)P(i)等于0。例如在手写数字识别中,数字3的one-hot表示为[ 0 0 0 1 0 0 0 0 0 0 ][0001000000][0001000000][ 0​ 0​ 0​ 1​ 0​ 0​ 0​ 0​ 0​ 0​ ]对于交叉熵来说,H ( p , q ) = − ∑ i P ( i ) l o g Q ( i ) = − P ( 3 ) l o g Q ( 3 ) = − l o g Q ( 3 ) H(p,q)=- \sum\limits_iP(i)logQ(i)=-P(3)logQ(3)=-logQ(3)H(p,q)=− i∑​ P(i)logQ(i)=−P(3)logQ(3)=−logQ(3)发现H ( p , q ) H(p,q)H(p,q)的计算不依赖于P PP矩阵,而仅仅与P PP的真实类别的i n d e x indexindex有关五、总结所以,我的理解是,在one-hot编码的前提下,在pytorch代码中target不需要以one-hot形式表示,而是直接用scalar,scalar的值则是真实类别的index。所以交叉熵的公式可表示为:
最新发布
04-09
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值