CNN中权值共享理解,局部引用与全局引用

病情描述:近10天,发现按压右肋骨下有很硬的小包块,胃也不舒服,咨询一下专家

简介

  • 通常不必关心内部对象释放内存问题,因为已经交由 Java虚拟机 负责。
  • JNI 支持3中不透明引用: 局部引用全局引用弱全局引用

第一步,针对一个神经元,一幅640*360图像,一个神经元要对应640*360个像素点,即一个神经元对应全局图像,全连接的话一个神经元就有640*360个参数;

你的情况,首先考虑是皮脂腺囊肿或脂肪瘤,可以出现局部包块。你需要去医院做个局部彩超,确诊一下。如果是这个病,需要行手术治疗。术后不要喝酒,抽烟,保持局部清洁,按时换药,口服消炎药,预防感染。

局部引用

第二步,然而,图像的空间联系是局部的,就像人是通过一个局部的感受野去感受外界图像一样,每一个神经元都不需要对全局图像做感受,每个神经元只感受局部的图像区域,然后在更高层,将这些不同局部的神经元综合起来就可以得到全局信息。假如每个局部感受野10*10,每个局部感受野只需要和10*10的局部图像连接,这样一个神经元就只需要10*10个参数;

简介

  • 局部引用 可以由一系列 JNI函数 获取。
  • 局部引用 会在原生函数返回时被自动释放。
  • 只能在创建了 局部引用 的线程使用此 局部引用 ,不该使用全局变量存储 局部引用 指望别的线程能使用此引用。
  • 不要使用 静态存储生命周期变量 (即全局变量,局部静态变量)存储 局部引用 ,然后指望下次使用此静态变量时, 局部引用 仍然有效。
  • 局部引用 可以通过DeleteLocalRef()手动释放。

第三步,全局图像是640*360,但局部图像只有10*10大小,10*10个参数只针对局部图像,如果全局图像中各个局部图像之间权值共享的话,即10*10个参数在不同局部图像上参数应用相同的话,则在全局图像上通过全局共享则只需要10*10个参数;

手动释放局部引用

  • 通常情况下不必手动释放 局部引用 ,但是在以下情况则需要手动释放:
    • 创建了大量的 局部引用 ,如数组元素等,可以每用完一个释放一个。
    • 这个 局部引用 是在 效用函数 中创建的。
    • 这个 局部引用 指向一个巨大的对象。
    • 原生函数永远都不会返回,比如一个永久循环。

第四步,10*10个参数只针对一个神经元,要是有100万个神经元,则需要100万*10*10个参数,神经元多后,参数还是太大,如果每个神经元的这10*10个参数相同呢,这样就还是只需要10*10参数,因而经过局部感受野到权值共享再到每个神经元的10*10个参数相同,不管图像多大,不管每层神经元个数多少,而两层间连接还是只需要求解10*10个参数;

管理局部引用

第五步,由于只有一个滤波器,只提取了一种特征,特征也太少了。一种滤波器也就是一种卷积核就是提取图像一种特征,例如某个方向的边缘。那么我们需要提取不同特征怎么办,多加几个滤波器不就行了。假设我们加到100种滤波器,每种滤波器的参数不一样,表示提取输入图像不同特征,例如不同边缘。这样不同滤波器去卷积图像就得到不同特征的放映,我们称之为Feature Map,所以100中卷积核就有100个Feature Map,这100个Feature Map就组成了一层神经元。我们这一层有多少个参数到这时候就明了吧,100种卷积核  *   每种卷积核100个参数  = 100 * 100 = 10000个参数。

函数解说

  • EnsureLocalCapacity():用于确保有足够的空间给某个数量的 局部引用
  • PushLocalFrame() / PopLocalFrame()
    • 开始一段区域,在此区域创建的 局部引用 都会被记录,在随后调用PopLocalFrame()时会释放这段区域创建的所有 局部引用
    • PushLocalFrame() / PopLocalFrame()十分有效率,可以在进入 效用函数 时调用PushLocalFrame(),在return的地方调用PopLocalFrame()。若有多个return的时候,每个return处都需要调用PopLocalFrame()
    • PopLocalFrame()的第二个参数十分有用,在碰见需要释放所有 局部引用 除了其中一个保留并返回时,可以将此引用传递PopLocalFrame()第二个参数。
  • NewLocalRef():用于在 效用函数 中返回一个 局部引用

最后,刚才说每一个隐藏层的参数个数和隐藏层的神经元个数无关,只和滤波器大小和滤波器种类数有关,那么隐藏层的神经元个数怎么确定呢?它和原图像,也就是输入的大小(神经元个数)、滤波器的大小和滤波器在图像中的滑动步长都有关!假如我的图像是1000*1000像素的,而滤波器大小是10*10,假设步长为10,即滤波器没有重叠,这样隐藏层的神经元个数就是 1000 * 1000 / (10*10) = 100*100个神经元(如果步长为8,卷积核会重叠2个像素)。这只是一种滤波器,也就是一个Feature Map的神经元个数哦,如果100个Feature Map就是100倍了,

EnsureLocalCapacity()示例

// 开始获取局部引用前先确保有足够的空间
if (((* env)->EnsureLocalCapacity(env, len)) < 0) {
    // 内存不足
}
for (int i = 0; i < len; i++) {
    jstring jstr = (* env)->GetObjectArrayElement(env, arr, i);
}

需要注意一点,上面的讨论都没有考虑每个神经元的偏置部分,所以权值个数需要加1,这也是同一种滤波器共享。如滤波器10*10,卷积核个数6,则参数个数为:

PushLocalFrame() / PopLocalFrame()示例

jobject f(JNIEnv * env, ...) {
    jobject result = NULL;
    // 开始一段区域,并保证有足够的空间
    if ((*env)->PushLocalFrame(env, 10) < 0) {
        return NULL;
    }
    // ...
    result = ...;
    if (...) {
        // 结束这段区域,区域中获取的局部引用全部释放,result通过传入传出得到了保留
        result = (* env)->PopLocalFrame(env, result);
        return result;
    }
    // 保证所有的return前都调用了PopLocalFrame
    result = (* env)->PopLocalFrame(env, result);
    return result;
}

(10*10 +1) * 6 = 606.

NewLocalRef()示例

jstring MyNewString(JNIEnv * env, jchar * chars, jint len) {
    static jstring result;
    if (wstrncmp("CommonString", chars, len) == 0) {
        static jstring cachedString = NULL;
        if (cachedString == NULL) {
            jstring cachedStringLocal = ...;
            cachedString = (* env)->NewGlobalRef(env, cachedStringLocal);
        }
        // 将全局引用cachedString转换为局部引用后返回
        return (* env)->NewLocalRef(env, cachedString);
    }
    return result;
}

全局引用

  • 局部引用 不一样, 全局引用 只需要一个 JNI函数 便能获取,即NewGlobalRef()
  • 可以使用 全局引用 在多个函数调用间或多个线程间共享。
  • 全局引用 不会被自动释放,需要手动使用DeleteGlobalRef()释放。
jstring MyNewString(JNIEnv * env, jchar * chars, jint len) {
    // 可以使用局部静态变量来存储全局引用
    static jclass stringClass = NULL;
    if (stringClass == NULL) {
        jclass localRefCls = (* env)->FindClass(env, "java/lang/String");
        if (localRefCls == NULL) {
            return NULL;
        }

        // 通过局部引用来获取全局引用
        stringClass = (* env)->NewGlobalRef(env, localRefCls);
        // 先释放局部引用,再检查是否成功获取全局引用
        // 因为无论是否成功,局部引用都不再有效
        (* env)->DeleteLocalRef(env, localRefCls);
        if (stringClass == NULL) {
            return NULL;
        }
    }
    // ...
}

弱全局引用

  • 弱全局引用 使用NewWeakGlobalRef()来获取。
  • 弱全局引用全局引用 一样,可以在多个函数调用间或多个线程间共享。
  • 弱全局引用 可能会被垃圾回收器回收。
  • 弱全局引用 虽然可能会被自动释放,但仍然需要使用DeleteWeakGlobalRef()来手动释放。
  • java.lang.String这样的系统类永远不会被垃圾回收器回收。
JNIEXPORT void JNICALL Java_mypkg_MyCls_f(JNIEnv * env, jobject self) {
    static jclass myCls2 = NULL;
    if (myCls2 == NULL) {
        jclass myCls2Local = (* env)->FindClass(env, "mypkg/MyCls2");
        if (myCls2Local == NULL) {
            return;
        }

        // 使用局部引用获取弱全局引用
        myCls2 = (* env)->NewWeakGlobalRef(env, myCls2Local);
        if (myCls2 == NULL) {
            return;
        }
    }
    // ...
}

比较引用

  • 可以使用IsSameObject()来检查两个引用(局部、全局或弱全局)是否指向同一个对象。如果返回JNI_TRUE,则两个引用指向同一个对象。如果返回JNI_FALSE,则相反。
  • 若引用的值为NULL,则此引用指向 Java 中的null对象。
  • 对于 局部引用全局引用 可以通过(* env)->IsSameObject(env, obj, NULL)来判断引用是否指向null对象。若返回JNI_TRUE则指向null对象。
  • 对于 弱全局引用 可以通过(* env)->IsSameObject(env, wobj, NULL)来检查到更多。若返回JNI_TRUE,则表明引用要么指向null对象,要么被垃圾回收器回收了。无论哪一种,引用都不能再使用了。

管理引用的规则

  • JNI 代码中有三种类型的函数:
    1. JNI函数JNIAPI 函数。
    2. 原生函数 :实现 Java 代码中 native方法声明C 函数。
    3. 效用函数C 工具函数。
  • 对于 原生函数 ,应该注意:
    • 避免大量创建 局部引用
    • 避免不返回的 原生函数 创建了不必要的 局部引用
  • 对于 效用函数 ,应该注意:
    • 尽量避免内存泄漏。
    • 如果此 效用函数 返回 原始类型 ,应该释放所有的 局部引用
    • 如果此 效用函数 返回 引用类型 ,应该释放除了要返回的 引用类型 以外所有的 局部引用
    • 不应该偶尔返回 局部引用 ,偶尔返回 全局引用 ,因为调用此 效用函数 的调用者需要知道返回的 引用类型 ,以便管理这个引用。
    • 可以使用NewLocalRef()来保证 效用函数 返回的 引用类型 总是 局部引用
while (JNI_TRUE) {
    // GetInfoString是一个效用函数,infoString是由此效用函数创建的
    jstring infoString = GetInfoString(info);

    // 使用infoString

    // 使用完毕后,在此处需要根据infoString的引用类型(局部引用、全局引用或弱全局引用)来释放infoString。
}

本文由91599金沙游艺场发布于疾病预防,转载请注明出处:CNN中权值共享理解,局部引用与全局引用

相关阅读