2024年2月

写在开头

一点题外话

其实最近在不断的更新Java的知识,从基础到进阶,以及计算机基础、网络、WEB、数据库、数据结构、Linux、分布式等等内容,预期写成一个既可以学习提升又可以面试找工作的《Java成长之路》!算是对自己学习的总结(笔记),也算是一种程序猿的记忆,现在大环境那么差,万一哪天转行了,还能当做一种记忆。

以往的博客都是按照知识点的顺序发的,但今天一个大一的粉丝私信问JS异步编程内容,决定先理一理这块内容,想着后面如果还是有类似的咨询,则会破坏原有发文顺序,进行交叉更新,最后,会将所有内容汇总成PDF,发给需要的朋友哈。

回到话题

ok,那我们现在就一起来学习一下JS的异步编程吧,异步的诞生是因为同步带来的诸多不便,我们都知道JavaScript是单线程时间循环模型,
同步
如同流水线,一步步的往下走中间有步骤失败,下面的功能就无法进行,这显然不是我们想要的。我们想要的是,一个程序在执行的过程中,我们可以去干其他的,不需要等待它的完成再往下执行,这就是
异步

异步编程

什么是异步编程?刚刚的大白话已经点名,为了能更直观的感受,我们看下面这段代码:
【代码示例1】

const i = 'HelloWord';
setTimeout(() => {
    console.log('JavaBuild')
}, 2000);
console.log(i)

我们上面说了JS是单线程的,在这个主线程中,我们通过setTimeout()这个回调函数,实现了异步。控制台运行的时候我们就会发现,HelloWord先打印,等2秒后打印JavaBuild。
这种通过简单的回调函数实现异步,在复杂的代码逻辑中,多个异步操作时会带来“回调地狱”,因此同样不常用。

Promise

为了优化(注意是优化,并不是彻底解决回调地狱)多层嵌套的异步调用,
Promise(承诺)
应运而生。
Promise 对象表示异步操作最终的完成(或失败)以及其结果值。

一个 Promise 是一个代理,它代表一个在创建 promise 时不一定已知的值。它允许你将处理程序与异步操作的最终成功值或失败原因关联起来。这使得异步方法可以像同步方法一样返回值:异步方法不会立即返回最终值,而是返回一个 promise,以便在将来的某个时间点提供该值

通过Promise我们可以实现相同的效果
【代码示例2】

new Promise((resolveOuter) => {
    resolveOuter(
        new Promise((resolveInner) => {
            setTimeout (() => {
                console.log("resolveInner");
            }, 1000 );

        }),
    );
    console.log('resolveOuter')
});

此 Promise 在创建时已经被解决(因为 resolveOuter 是同步调用的),但它是用另一个 Promise 解决的,因此在内部 Promise 兑现的 1 秒之后才会被兑现。

Promise的链式调用

Promise.prototype.then()、Promise.prototype.catch() 和 Promise.prototype.finally() 方法用于将进一步的操作与已敲定的 Promise 相关联。由于这些方法返回 Promise,因此它们可以被链式调用。
【代码示例3】

const myPromise = new Promise((resolve, reject) => {
  setTimeout(() => {
    resolve("JavaBuild");
  }, 300);
});

myPromise
  .then(handleFulfilledA)
  .then(handleFulfilledB)
  .then(handleFulfilledC)
  .catch(handleRejectedAny);

关于Promise相关的详细内容这里不再赘述了,因为真实的使用中,我们一般也不会这样写,感兴趣的同学,可以看这篇文章
一文看懂Promise

async / await

在ES8语法中带来了两个新词
async

await
,进一步的通过异步函数实现异步操作,并将Promise(期约)应用于JavaScript函数的结果。

async
关键字用于声明异步函数,它可以在函数声明,函数表达式还有箭头函数上使用,使用async关键字可以让函数具有异步特征,在实际中它需要和await配合使用。
【代码示例4】

    async function msg(){}
    let jackson = async function(){}
    let jackson = async ()=>{}

一旦定义了一个函数作为一个异步函数,我们就可以使用
await
关键词。这个关键词放在回调的Promise之前,将会暂停执行函数,直到Promise执行或拒绝。
【代码示例5】

 async function msg(){
        let p = new Promise((resolve,reject)=>setTimeout(resolve,1000,'JavaBuild'));
        console.log(await p);
    }
 msg();

await关键字其实很简单,js运行在碰到await关键字时,会记录在哪里暂停执行。等到await右边的值可以使用了,就是处理完回调了,js会向消息列对中推送一个任务,这个任务会恢复异步函数的执行。这样的话,即使await后面跟着一个立即可用的值,函数的其余部分也会被异步求值。

【代码示例6】

    async function fun01(){
        console.log(await Promise.resolve('第一名'));
    }
    async function fun02(){
        console.log(await '第二名');
    }
    async function fun03(){
        console.log('第三名');
    }
    fun01();
    fun02();
    fun03();

大家猜一猜最终打印出来的结果顺序是啥?

//因为await原因,fun01,fun02进入暂停队列,fun03先执行,返回已解决(已敲定)的Promise后,进入fun01,执行返回后,在进入fun02。
第三名
第一名
第二名

ok,以上就是简单的JS异步编程啦!

结尾彩蛋

如果本篇博客对您有一定的帮助,大家记得
留言+点赞+收藏
呀。原创不易,转载请联系Build哥!

分类模型
评估时,
scikit-learn
提供了
混淆矩阵

分类报告
是两个非常实用且常用的工具。
它们为我们提供了详细的信息,帮助我们了解模型的优缺点,从而进一步优化模型。

这两个工具之所以单独出来介绍,是因为它们的输出内容特别适合用在模型的评估报告中。

1. 混淆矩阵

混淆矩阵

Confusion Matrix
)用于直观地展示模型预测结果与实际标签之间的对应关系。
它是一个表格,其

表示实际的类别标签,而

表示模型预测的类别标签。

通过
混淆矩阵
,可以清晰地看到模型的哪些预测是正确的,哪些是错误的,以及错误预测的具体分布情况。

1.1. 使用示例

下面用
手写数字识别
的示例,演示最后如何用
混淆矩阵
来可视化的评估模型训练结果的。
首先,读取手写数字数据集(这个数据集是
scikit-learn
中自带的):

import matplotlib.pyplot as plt
from sklearn import datasets

# 加载手写数据集
data = datasets.load_digits()

_, axes = plt.subplots(nrows=2, ncols=4, figsize=(10, 6))
for ax, image, label in zip(np.append(axes[0], axes[1]), data.images, data.target):
    ax.set_axis_off()
    ax.imshow(image, cmap=plt.cm.gray_r, interpolation="nearest")
    ax.set_title("目标值: {}".format(label))

image.png

然后,用支持向量机来训练数据,得到一个分类模型(
reg
):

from sklearn.svm import SVC

n_samples = len(data.images)
X = data.images.reshape((n_samples, -1))
y = data.target

# 定义
reg = SVC()

# 训练模型
reg.fit(X, y)

最后,用得到的
分类模型
来预测数据,再用
混淆矩阵
来分析
预测值

真实值

from sklearn.metrics import confusion_matrix, ConfusionMatrixDisplay

# 用训练好的模型进行预测
y_pred = reg.predict(X)

cm = confusion_matrix(y, y_pred)
g = ConfusionMatrixDisplay(confusion_matrix=cm)
g.plot()

plt.show()

image.png
混淆矩阵
中,
横轴
是预测值,
纵轴
是真实值。
对角线上预测值与真实值符合的情况,可以看出模型分类效果不错,大部分数据都能正确分类的。

也有极个别分类错误的情况,比如:

  • 8
    被识别成
    1
    的错误有
    2个
  • 5
    被识别成
    9
    的错误有
    1个
  • 9
    被识别成
    3
    的错误有
    1个
  • ... ... 等等

2. 分类报告

分类报告
提供了模型在各个类别上的详细性能指标。
通常包括
准确率

Precision
)、
召回率

Recall
)、
F1分数

F1-Score
)等评估指标,这些指标能够帮助我们更全面地了解模型的性能。

2.1. 使用示例

基于上面训练的手写数字识别模型,看看模型的各项指标。

from sklearn.metrics import classification_report

# 这里的y 和 y_pred 是上一节示例中的值
report = classification_report(y, y_pred)
print(report)

image.png
报告中列出了手写数字
0~9
的识别情况。

3. 总结

总的来说,
分类报告

混淆矩阵
一起使用,能够更全面地评估模型的性能,指导模型的优化和改进。
而且它们生成的评估表格和图形,也能够应用于我们的分析报告中。

Softmax 回归

有一种
logistic
回归的一般形式,叫做
Softmax
回归,能让在试图识别某一分类时做出预测,或者说是多种分类中的一个,不只是识别两个分类,来一起看一下。

假设不单需要识别猫,而是想识别猫,狗和小鸡,把猫加做类1,狗为类2,小鸡是类3,如果不属于以上任何一类,就分到“其它”或者说“以上均不符合”这一类,把它叫做类0。这里显示的图片及其对应的分类就是一个例子,这幅图片上是一只小鸡,所以是类3,猫是类1,狗是类2,猜这是一只考拉,所以以上均不符合,那就是类0,下一个类3,以此类推。将会用符号表示,会用大写的
\(C\)
来表示的输入会被分入的类别总个数,在这个例子中,有4种可能的类别,包括“其它”或“以上均不符合”这一类。当有4个分类时,指示类别的数字,就是从0到
\(C-1\)
,换句话说就是0、1、2、3。

在这个例子中,将建立一个神经网络,其输出层有4个,或者说
\(C\)
个输出单元,因此
\(n\)
,即输出层也就是
\(L\)
层的单元数量,等于4,或者一般而言等于
\(C\)
。想要输出层单元的数字告诉这4种类型中每个的概率有多大,所以这里的第一个节点(最后输出的第1个方格+圆圈)输出的应该是或者说希望它输出“其它”类的概率。在输入
\(X\)
的情况下,这个(最后输出的第2个方格+圆圈)会输出猫的概率。在输入
\(X\)
的情况下,这个会输出狗的概率(最后输出的第3个方格+圆圈)。在输入
\(X\)
的情况下,输出小鸡的概率(最后输出的第4个方格+圆圈),把小鸡缩写为
bc

baby chick
)。因此这里的
\(\hat y\)
将是一个
\(4×1\)
维向量,因为它必须输出四个数字,给这四种概率,因为它们加起来应该等于1,输出中的四个数字加起来应该等于1。

让的网络做到这一点的标准模型要用到
Softmax
层,以及输出层来生成输出,让把式子写下来,然后回过头来,就会对
Softmax
的作用有一点感觉了。

在神经网络的最后一层,将会像往常一样计算各层的线性部分,
\(z^{[l]}\)
这是最后一层的
\(z\)
变量,记住这是大写
\(L\)
层,和往常一样,计算方法是
\(z^{[l]} = W^{[l]}a^{[L-1]} + b^{[l]}\)
,算出了
\(z\)
之后,需要应用
Softmax
激活函数,这个激活函数对于
Softmax
层而言有些不同,它的作用是这样的。首先,要计算一个临时变量,把它叫做t,它等于
\(e^{z^{[l]}}\)
,这适用于每个元素,而这里的
\(z^{[l]}\)
,在的例子中,
\(z^{[l]}\)
是4×1的,四维向量
\(t=e^{z^{[l]}}\)
,这是对所有元素求幂,
\(t\)
也是一个4×1维向量,然后输出的
\(a^{[l]}\)
,基本上就是向量
\(t\)
,但是会归一化,使和为1。因此
\(a^{[l]} = \frac{e^{z^{[l]}}}{\sum_{j =1}^{4}t_{i}}\)
,换句话说,
\(a^{[l]}\)
也是一个4×1维向量,而这个四维向量的第
\(i\)
个元素,把它写下来,
\(a_{i}^{[l]} = \frac{t_{i}}{\sum_{j =1}^{4}t_{i}}\)
,以防这里的计算不够清晰易懂,马上会举个例子来详细解释。

来看一个例子,详细解释,假设算出了
\(z^{[l]}\)

\(z^{[l]}\)
是一个四维向量,假设为
\(z^{[l]} = \begin{bmatrix} 5 \\ 2 \\ - 1 \\ 3 \\ \end{bmatrix}\)
,要做的就是用这个元素取幂方法来计算
\(t\)
,所以
\(t =\begin{bmatrix} e^{5} \\ e^{2} \\ e^{- 1} \\ e^{3} \\ \end{bmatrix}\)
,如果按一下计算器就会得到以下值
\(t = \begin{bmatrix} 148.4 \\ 7.4 \\ 0.4 \\ 20.1 \\ \end{bmatrix}\)
,从向量
\(t\)
得到向量
\(a^{[l]}\)
就只需要将这些项目归一化,使总和为1。如果把
\(t\)
的元素都加起来,把这四个数字加起来,得到176.3,最终
\(a^{[l]} = \frac{t} {176.3}\)

例如这里的第一个节点,它会输出
\(\frac{e^{5}}{176.3} =0.842\)
,这样说来,对于这张图片,如果这是得到的
\(z\)
值(
\(\begin{bmatrix} 5 \\ 2 \\ - 1 \\ 3 \\ \end{bmatrix}\)
),它是类0的概率就是84.2%。下一个节点输出
\(\frac{e^{2}}{176.3} =0.042\)
,也就是4.2%的几率。下一个是
\(\frac{e^{- 1}}{176.3} =0.002\)
。最后一个是
\(\frac{e^{3}}{176.3} =0.114\)
,也就是11.4%的概率属于类3,也就是小鸡组,对吧?这就是它属于类0,类1,类2,类3的可能性。

神经网络的输出
\(a^{[l]}\)
,也就是
\(\hat y\)
,是一个4×1维向量,这个4×1向量的元素就是算出来的这四个数字(
\(\begin{bmatrix} 0.842 \\ 0.042 \\ 0.002 \\ 0.114 \\ \end{bmatrix}\)
),所以这种算法通过向量
\(z^{[l]}\)
计算出总和为1的四个概率。

如果总结一下从
\(z^{[l]}\)

\(a^{[l]}\)
的计算步骤,整个计算过程,从计算幂到得出临时变量
\(t\)
,再归一化,可以将此概括为一个
Softmax
激活函数。设
\(a^{[l]} = g^{[l]}(z^{[l]})\)
,这一激活函数的与众不同之处在于,这个激活函数
\(g\)
需要输入一个4×1维向量,然后输出一个4×1维向量。之前,的激活函数都是接受单行数值输入,例如
Sigmoid

ReLu
激活函数,输入一个实数,输出一个实数。
Softmax
激活函数的特殊之处在于,因为需要将所有可能的输出归一化,就需要输入一个向量,最后输出一个向量。

那么
Softmax
分类器还可以代表其它的什么东西么?来举几个例子,有两个输入
\(x_{1}\)

\(x_{2}\)
,它们直接输入到
Softmax
层,它有三四个或者更多的输出节点,输出
\(\hat y\)
,将向展示一个没有隐藏层的神经网络,它所做的就是计算
\(z^{[1]} = W^{[1]}x + b^{[1]}\)
,而输出的出
\(a^{[l]}\)
,或者说
\(\hat y\)

\(a^{[l]} = y = g(z^{[1]})\)
,就是
\(z^{[1]}\)

Softmax
激活函数,这个没有隐藏层的神经网络应该能让对
Softmax
函数能够代表的东西有所了解。

这个例子中(左边图),原始输入只有
\(x_{1}\)

\(x_{2}\)
,一个
\(C=3\)
个输出分类的
Softmax
层能够代表这种类型的决策边界,请注意这是几条线性决策边界,但这使得它能够将数据分到3个类别中,在这张图表中,所做的是选择这张图中显示的训练集,用数据的3种输出标签来训练
Softmax
分类器,图中的颜色显示了
Softmax
分类器的输出的阈值,输入的着色是基于三种输出中概率最高的那种。因此可以看到这是
logistic
回归的一般形式,有类似线性的决策边界,但有超过两个分类,分类不只有0和1,而是可以是0,1或2。

这是(中间图)另一个
Softmax
分类器可以代表的决策边界的例子,用有三个分类的数据集来训练,这里(右边图)还有一个。对吧,但是直觉告诉,任何两个分类之间的决策边界都是线性的,这就是为什么看到,比如这里黄色和红色分类之间的决策边界是线性边界,紫色和红色之间的也是线性边界,紫色和黄色之间的也是线性决策边界,但它能用这些不同的线性函数来把空间分成三类。

来看一下更多分类的例子,这个例子中(左边图)
\(C=4\)
,因此这个绿色分类和
Softmax
仍旧可以代表多种分类之间的这些类型的线性决策边界。另一个例子(中间图)是
\(C=5\)
类,最后一个例子(右边图)是
\(C=6\)
,这显示了
Softmax
分类器在没有隐藏层的情况下能够做到的事情,当然更深的神经网络会有
\(x\)
,然后是一些隐藏单元,以及更多隐藏单元等等,就可以学习更复杂的非线性决策边界,来区分多种不同分类。

本文介绍基于
C++
语言
GDAL
库,
批量读取
大量栅格遥感影像文件,并生成
各像元数值的时间序列
数组的方法。

首先,我们来明确一下本文所需实现的需求。现在有一个文件夹,其中包含了很多不同格式的文件,如下图所示。

其中,我们首先需要遍历这一文件夹,遴选出其中所有类型为
.bmp
格式的栅格遥感影像文件(一共有
6
个),并分别读取文件(已知这些遥感影像的行数、列数都是一致的);随后,将
不同遥感影像

同一个位置的像素
的数值进行分别读取,并存储在一个数组中。例如,最终我们生成的第一个数组,其中共有
6
个元素,分别就是上图所示文件夹中
6
景遥感影像各自
(0,0)
位置的像元数值;生成的第二个数组,其中也是
6
个元素,分别就是
6
景遥感影像各自
(1,0)
位置的像元数值,以此类推。其中,显然我们得到的
数组个数
,就是遥感影像
像元的个数
。此外,这里
6
景遥感影像的
排序
,是按照
文件名称的升序
来进行的。

明确了具体需求,接下来就可以开始代码的实践。其中,本文分为两部分,第一部分为代码的分段讲解,第二部分为完整代码。

此外,本文是基于
GDAL
库来实现栅格数据读取的;具体
GDAL
库的配置方法大家可以参考文章
在Visual Studio中部署GDAL库的C++版本(包括SQLite、PROJ等依赖)

1 代码分段介绍

1.1 代码准备

这一部分主要是代码的
头文件

命名空间
与我们自行撰写的
自定义函数
get_need_file()
的声明;具体代码如下所示。

#include <iostream>
#include <vector>
#include <io.h>
#include "gdal_priv.h"

using namespace std;

void get_need_file(string path, vector<string>& file, string ext);

其中,由于我们在接下来的代码中需要用到容器
vector
这一数据类型,因此首先需要添加
#include <vector>
;同时,我们在接下来的代码中需要用到头文件
io.h
中的部分函数(主要都是一些与计算机系统、文件管理相关的函数),因此需要添加
#include <io.h>
;此外,我们是基于
GDAL
库来实现栅格数据读取的,因此需要添加
#include "gdal_priv.h"

接下来,这里声明了一个自定义函数
get_need_file()
,具体我们在本文
1.2
部分介绍。

1.2 栅格文件筛选

由于我这里几乎将全部的代码都放在了主函数中,因此这一部分就先介绍代码
main()
函数的第一部分,亦即栅格文件的遴选部分;具体代码如下所示。

int main() {
	string file_path = R"(E:\02_Project\02_ChlorophyllProduce\01_Data\00_Test)";
	vector<string> my_file;
	string need_extension = ".bmp";
	get_need_file(file_path, my_file, need_extension);
	int file_size = my_file.size();
	if (file_size == 0)
	{
		cout << "No file can be found!" << endl;
	}
	else
	{
		cout << "Find " << file_size << " file(s).\n" << endl;
	}

这一部分主要就是做好调用自定义函数
get_need_file()
的变量准备,并调用
get_need_file()
函数,得到指定文件夹下的栅格文件;随后,将栅格文件的筛选结果进行输出。这一部分的具体代码介绍,大家查看文章
C++遴选出特定类型的文件或文件名符合要求的文件
即可,这里就不再赘述。

1.3 栅格文件读取

这一部分主要是基于
GDAL
库,循环读取前述文件夹中的每一个栅格遥感影像文件。

	int nXSize, nYSize;
	float** pafScanline = new float* [file_size];
	int pic_index = 1;
	for (auto x : my_file)
	{
		GDALDataset* poDataset;
		GDALAllRegister();
		CPLSetConfigOption("GDAL_FILENAME_IS_UTF8", "NO");
		poDataset = (GDALDataset*)GDALOpen(x.c_str(), GA_ReadOnly);
		if (poDataset == NULL)
		{
			cout << "Open File " << x << " Error!" << endl;
		}
		else
		{
			cout << "Open File " << x << " Success!" << endl;
		}

		GDALRasterBand* poBand;
		poBand = poDataset->GetRasterBand(1);
		nXSize = poBand->GetXSize();
		nYSize = poBand->GetYSize();
		cout << nXSize << "," << nYSize << "\n" << endl;
		pafScanline[pic_index - 1] = new float[nXSize * nYSize];
		poBand->RasterIO(GF_Read, 0, 0, nXSize, nYSize, pafScanline[pic_index - 1], nXSize, nYSize, GDT_Float32, 0, 0);

		pic_index ++;
	}

其中,
nXSize

nYSize
分别表示栅格遥感影像的列数与行数,
pafScanline
是我们读取栅格遥感影像文件所需的变量,之后读取好的遥感影像数据就会存放在这里;由于我们有多个栅格文件需要读取,因此通过
for
循环来实现批量读取的操作,并通过
pic_index
这个变量作为每一次读取文件的计数。

在这里,
float** pafScanline = new float* [file_size];
这句代码表示我们将
pafScanline
作为一个
指向指针的指针
的数组;在后期读取遥感影像数据后,
pafScanline[0]

pafScanline[1]
一直到
pafScanline[5]
,这
6
个数值同样分别是指针,分别指向存储
6
景遥感影像数据的地址。这里我们通过
new
实现对
pafScanline
内存的动态分配,因为我们在获取栅格遥感影像的景数(也就是文件夹中栅格遥感影像文件的个数)之前,也不知道具体需要给
pafScanline
这一变量分配多少的内存。此外,在
for
循环中,我们还对
pafScanline[0]

pafScanline[1]
一直到
pafScanline[5]
同样进行了动态内存分配,因为我们在获取每一景栅格遥感影像的行数与列数之前,同样是不知道需要给
pafScanline[x]

6
个数组变量分配多少内存的。

随后,
for
循环中的其他部分,就是
GDAL
库读取遥感影像的基本代码。读取第一景遥感影像数据后,我们将数据保存至
pafScanline[0]
,并随后进行第二次循环,读取第二景遥感影像数据,并将其数据保存至
pafScanline[1]
中,随后再次循环;以此类推,直至读取
6
景遥感影像完毕。

如果大家只是需要实现
C++
批量读取栅格遥感影像数据,那么以上操作就已经实现了大家的需求。其中,显然
pafScanline[0]
就是第一景遥感影像数据,
pafScanline[1]
就是第二景遥感影像数据,
pafScanline[2]
就是第三景遥感影像数据,以此类推。

1.4 像元时间序列数组生成

这一部分则是基于以上获取的各景遥感影像数据读取结果,进行每一个
像元数值的时间序列数组
生成。

	float** pixel_paf = new float* [nXSize * nYSize];
	for (int pixel_num = 0; pixel_num < nXSize * nYSize; pixel_num++)
	{
		pixel_paf[pixel_num] = new float[file_size];
		for (int time_num = 0; time_num < file_size; time_num++)
		{
			pixel_paf[pixel_num][time_num] = pafScanline[time_num][pixel_num];
		}
	}

这一部分的代码思路其实也非常简单,就是通过两个
for
循环,将原本
一共
6

的、每一个表示
每一景遥感影像中全部数据
的数组,转变为
一共
X

的(
X
表示每一景遥感影像的像元总个数)、每一个表示
每一个位置的像元在
6
景遥感影像中的各自数值
的数组。

在这里,由于同样的原因,我们对
pixel_paf
亦进行了内存的动态分配。

1.5 输出测试与代码收尾

这一部分主要是输出一个我们刚刚配置好的
像元数值时间序列数组
,从而检查代码运行结果是否符合我们的要求;此外,由于前面我们对很多变量进行了动态内存分配,因此需要将其
delete
掉;同时,这里还可以对前面我们定义的指向指针的指针赋值为
NULL
,这样子其就不能再指向任何地址了,即彻底将其废除。

	for (int i = 0; i < file_size; i++)
	{
		cout << pixel_paf[0][i] << "," << endl;
	}

	delete[] pafScanline;
	delete[] pixel_paf;
	pafScanline = NULL;
	pixel_paf = NULL;

	return 0;
}

至此,代码的主函数部分结束。

1.6 自定义函数

这一部分是我们的自定义函数
get_need_file()

void get_need_file(string path, vector<string>& file, string ext)
{
	intptr_t file_handle = 0;
	struct _finddata_t file_info;
	string temp;
	if ((file_handle = _findfirst(temp.assign(path).append("/*" + ext).c_str(), &file_info)) != -1)
	{
		do
		{
			file.push_back(temp.assign(path).append("/").append(file_info.name));
		} while (_findnext(file_handle, &file_info) == 0);
		_findclose(file_handle);
	}
}

如前所述,这一部分的具体代码介绍,大家查看文章
C++遴选出特定类型的文件或文件名符合要求的文件
即可,这里就不再赘述。

2 完整代码

本文所需用到的完整代码如下所示。

#include <iostream>
#include <vector>
#include <io.h>
#include "gdal_priv.h"

using namespace std;

void get_need_file(string path, vector<string>& file, string ext);

int main() {
	string file_path = R"(E:\02_Project\02_ChlorophyllProduce\01_Data\00_Test)";
	vector<string> my_file;
	string need_extension = ".bmp";
	get_need_file(file_path, my_file, need_extension);
	int file_size = my_file.size();
	if (file_size == 0)
	{
		cout << "No file can be found!" << endl;
	}
	else
	{
		cout << "Find " << file_size << " file(s).\n" << endl;
	}

	int nXSize, nYSize;
	float** pafScanline = new float* [file_size];
	int pic_index = 1;
	for (auto x : my_file)
	{
		GDALDataset* poDataset;
		GDALAllRegister();
		CPLSetConfigOption("GDAL_FILENAME_IS_UTF8", "NO");
		poDataset = (GDALDataset*)GDALOpen(x.c_str(), GA_ReadOnly);
		if (poDataset == NULL)
		{
			cout << "Open File " << x << " Error!" << endl;
		}
		else
		{
			cout << "Open File " << x << " Success!" << endl;
		}

		GDALRasterBand* poBand;
		poBand = poDataset->GetRasterBand(1);
		nXSize = poBand->GetXSize();
		nYSize = poBand->GetYSize();
		cout << nXSize << "," << nYSize << "\n" << endl;
		pafScanline[pic_index - 1] = new float[nXSize * nYSize];
		poBand->RasterIO(GF_Read, 0, 0, nXSize, nYSize, pafScanline[pic_index - 1], nXSize, nYSize, GDT_Float32, 0, 0);

		pic_index ++;
	}

	float** pixel_paf = new float* [nXSize * nYSize];
	for (int pixel_num = 0; pixel_num < nXSize * nYSize; pixel_num++)
	{
		pixel_paf[pixel_num] = new float[file_size];
		for (int time_num = 0; time_num < file_size; time_num++)
		{
			pixel_paf[pixel_num][time_num] = pafScanline[time_num][pixel_num];
		}
	}

	for (int i = 0; i < file_size; i++)
	{
		cout << pixel_paf[0][i] << "," << endl;
	}

	delete[] pafScanline;
	delete[] pixel_paf;
	pafScanline = NULL;
	pixel_paf = NULL;

	return 0;
}

void get_need_file(string path, vector<string>& file, string ext)
{
	intptr_t file_handle = 0;
	struct _finddata_t file_info;
	string temp;
	if ((file_handle = _findfirst(temp.assign(path).append("/*" + ext).c_str(), &file_info)) != -1)
	{
		do
		{
			file.push_back(temp.assign(path).append("/").append(file_info.name));
		} while (_findnext(file_handle, &file_info) == 0);
		_findclose(file_handle);
	}
}

当我们运行上述代码后,将会出现如下所示的界面。

其中,会显示栅格遥感影像文件的筛选情况、具体文件名称及其各自的行号与列号;同时,最后一部分则是本文
1.5
部分提及的测试输出结果,其表示本文所用的
6
景遥感影像各自
(0,0)
位置处的像元数值。

至此,大功告成。

欢迎关注:疯狂学习GIS

Mol-Instructions: 面向大模型的大规模生物分子指令数据集

发表会议:ICLR 2024
论文标题:
Mol-Instructions: A Large-Scale Biomolecular Instruction Dataset for Large Language Models
论文链接:https://arxiv.org/pdf/2306.08018.pdf
代码链接:https://github.com/zjunlp/Mol-Instructions

引言

在自然语言处理(NLP)的众多应用场景中,大型语言模型(Large Language Model, LLM)展现了其卓越的文本理解与生成能力,不仅在传统的文本任务上成绩斐然,更在生物学、计算化学、药物研发等跨学科领域证明了其广泛的应用潜力。尽管如此,生物分子研究领域的特殊性—比如专用数据集的缺乏、数据标注的高复杂度、知识的多元化以及表示方式的不统一—仍旧是当前面临的关键挑战。针对这些问题,本文提出Mol-Instructions,这是一个针对生物分子领域各项研究任务定制的指令数据集。

构建 Mol-Instructions

Mol-Instructions的构建流程如下:
  • 借助LLM的能力,生成多样化的任务描述,模拟人类需求和表达的多样性。
  • 采用多种预处理策略,将现有数据库中的数据转化为可用的指令数据。
  • 利用模版将结构化的功能注释转换为易于理解的文本。
  • 对小分子和蛋白质序列进行严格的质量控制,以排除化学无效和冗余的序列。

概览 Mol-Instructions

Mol-Instructions数据集共计含2043K条指令数据,覆盖了小分子、蛋白质以及生物分子文本三大领域的17个关键任务,包含了不同复杂度和结构的生物分子及丰富的文本描述。

  • 小分子指令:深度探索小分子的固有属性与行为,研究化学反应和分子设计的核心挑战。理解和预测小分子的化学特性,优化分子设计,提高化学反应预测的准确性和效率。其目标是在化学和药物设计领域加速药物的研发进程,同时降低研发成本。
  • 蛋白质指令:主要解决蛋白质设计和功能相关的问题。旨在预测蛋白质结构域、功能及活性,通过文本指令推动蛋白质设计。对于疾病的诊断、治疗以及新药的研发工作具有一定的价值。
  • 生物文本指令:侧重于生物信息学和化学信息学领域的自然语言处理任务。旨在从生物医学文献中提取和解析关键信息,支持研究人员快速获取知识、便于进行查询。

  • 图(a-d)揭示了分子的多维特征。Bertz复杂度是评估分子复杂度的关键指标。分子量反映了分子的大小和复杂性,对众多化学反应具有决定性影响。原子计数揭示了分子的规模和复杂度,进而影响其稳定性和反应性。环计数则提供了结构复杂度和潜在稳定性的视角,对理解化学反应性和生物活性潜力至关重要。图(e-j)探究了蛋白质的特性。图(e-g)体现了蛋白质序列长度的不同分布。根据NCBI分类,这些蛋白质覆盖了丰富的物种和实验菌株,包括13,563个蛋白质家族和643个超家族。图(h-j)关注功能特征,如结构域、基因本体和催化活性的注释。这些数据表现出显著的长尾分布,凸显了推断特定蛋白质功能的挑战,尤其是那些罕见功能的蛋白质。
  • 如表格所示,分子设计和蛋白质设计的文本描述提供了多维度的视角,涵盖从基本属性到特定应用场景的广泛特性。

实验分析

为评估Mol-Instructions对大型语言模型(LLMs)在理解和预测生物分子方面的助益,本文对LLaMA-7B模型进行了指令微调,并从多个角度进行了定量实验分析。实验结果显示,经Mol-Instructions微调的LLM在多种任务上的表现超越了其他大型模型,证明了Mol-Instructions在提升LLMs处理生物分子信息能力方面的关键作用。 然而,由于轻量微调过程的局限性,经过微调的LLM在分子生成任务上的表现并未超越现有的专用小型模型。这反映了LLM在追求广泛任务处理能力时,可能会牺牲掉某些专用小模型的专业性。

总结

Mol-Instructions能够有效评估和提升通用LLM从人类语言到生命语言的跨模态理解能力,显著增强了LLM对生物分子的认知。为后续更深入地研究生物分子设计与解决复杂生物学问题提供了重要的数据来源。由于文本与生物分子表示空间的本质差异以及LoRA训练策略的局限性,当前LLM在理解生物分子语言方面的熟练度还未能与其掌握人类语言的能力相媲美。未来,通过扩展模型词表或将生物分子语言视为一种新的模态进行集成,可能是进一步提升LLM在生物分子领域的理解深度和性能表现的关键。