Opencv学习记录【十】——神经网络MLP(多层感知机)
opencv为我们提供了多种机器学习方法,比如adaboost、svm、神经网络等。本文主要记录其神经网络的原理和用法(参考赵春江的“机器学习经典算法剖析基于opencv”,人民邮电出版社,214-220)
下面就以照片的方式贴出其原理:


OpenCV的人工神经网络是机器学习算法中的其中一种,使用的是多层感知器(Multi- Layer Perception,MLP),是常见的一种ANN算法。MLP算法一般包括三层,分别是一个输入层,一个输出层和一个或多个隐藏层的神经网络组成。每一层由一个或多个神经元互相连结。一个“神经元”的输出便是另一个“神经元”的输入。
OpenCV中的神经网络的训练,需要创建两个数据矩阵,一个是特征数据矩阵,一个是标签矩阵。但要注意的是标签矩阵是一个N*M的矩阵,N表示训练样本数,M是类标签。如果第i行的样本属于第j类,那么该标签矩阵的(i,j)位置为1。 OpenCV中ANN定义了CvANN_MLP类。使用ANN算法之前,必须先初始化参数,比如神经网络的层数、神经元数,激励函数、α和β。然后使用train函数进行训练,训练完成可以训练好的参数以xml的格式保存在本地文件夹。最后就可以使用predict函数来预测测试集。
如何利用opencv的神经网络api呢,很简单,可以看其官网或者按照下面的例子进行实践。
这个例子是人脸贴图利用opencv神经网络对脸部区域的位置和脸部器官位置信息进行训练,然后达到检测到脸部后通过神经网络即可定位相关器官。(这样免去了各个器官的检测,器官定位更快速)
代码说明:
先进行脸部、眼部、鼻子、嘴巴的检测(opencv提供的haar特征分类器)
都检测到后每帧的四个Rect信息存在一个文件中
读取该文件,face Rect 作为输入,其他作为输出,进行训练
源码:
#include <opencv2/core/core.hpp>
#include <opencv2/highgui/highgui.hpp>
#include <opencv2/ml/ml.hpp>
#include <iostream>
#include <fstream>
#include<string>
using namespace std;
using namespace cv;
//从眼部得到全脸 Mat eyesdata(Mat_<float>(1,3) << eyesRect.x,eyesRect.y,eyesRect.width)
cv::Mat getFace_Bpnet(char *bpxml,cv::Mat eyesdata)
{
CvANN_MLP bp; //bp网络
bp.load(bpxml);//读取模型
Mat facedata; //一组预测结果 facedata(1, 3,CV_32FC1)(Mat_<int>(1, 3) << 3,18,3)
bp.predict(eyesdata, facedata);
return facedata;
}
int test_cvNNapi(int group_data,int cmd)
{
CvANN_MLP bp; //bp网络
string datafile = "/home/jiang/Repositories/FaceDeal_Class/train_data/mydata.txt";
string testfile = "/home/jiang/Repositories/FaceDeal_Class/train_data/testdata.txt";
string resultfile = "/home/jiang/Repositories/FaceDeal_Class/train_data/resultfile.txt";
char buffer[50];
if(cmd == 1)
{ // trainning
int *IN_data = new int[3];
int *OUT_data = new int[3];
//建立一个标签矩阵
Mat labelsMat(group_data, 3, CV_32FC1);
//建立一个训练样本矩阵
Mat trainingDataMat(group_data, 3, CV_32FC1); // cols = 3 ; rows = group_data at(y,x)
fstream ifile;
int count_hang = 0;
ifile.open(datafile,ios::in);
for(int i=0;i<group_data;i++)
{
//每行格式 ,101,22,333,120,333,12,
ifile.getline(buffer, 50, '\n'); //getline(char *,int,char) 表示该行字符达到 50 个或遇到换行就结束;
int num_count = -1;
for(int j = 0; j < 50; ){
if(num_count >=5 )
break;
else if(buffer[j] == ','){
num_count++;
if(num_count < 3){
if(buffer[j+4] == ','){
IN_data[num_count] = (buffer[j+1] - '0')*100 + (buffer[j+2] - '0')*10 + (buffer[j+3] - '0')*1;
j = j+4;
}
else if (buffer[j+3] == ','){
IN_data[num_count] = (buffer[j+1] - '0')*10 + (buffer[j+2] - '0')*1;
j = j+3;
}
else if(buffer[j+2] == ','){
IN_data[num_count] = (buffer[j+1] - '0')*1;
j = j+2;
}
else
break;
}
else {
if(buffer[j+4] == ','){
OUT_data[num_count-3] = (buffer[j+1] - '0')*100 + (buffer[j+2] - '0')*10 + (buffer[j+3] - '0')*1;
j = j+4;
}
else if (buffer[j+3] == ','){
OUT_data[num_count-3] = (buffer[j+1] - '0')*10 + (buffer[j+2] - '0')*1;
j = j+3;
}
else if(buffer[j+2] == ','){
OUT_data[num_count-3] = (buffer[j+1] - '0')*1;
j = j+2;
}
else
break;
}
}
}
cout << count_hang++ <<endl;
cout << IN_data[0] << ","<< IN_data[1] << ","<< IN_data[2] << " ; "<< OUT_data[0] << ","<< OUT_data[1] << ","<< OUT_data[2] << endl;
//以上得出一行的数据
for(int k = 0;k < 3;k ++){
labelsMat.at<float>(i,k) = OUT_data[k]; //at (y,x)
trainingDataMat.at<float>(i,k) = IN_data[k];
}
}
cout << labelsMat <<endl;
ifile.close();
cout << "训练中..."<<endl;
// cout << labelsMat <<endl;
// cout << trainingDataMat <<endl;
/*定义神经网络及参数*/
//CvANN_MLP bp; //bp网络
CvANN_MLP_TrainParams params; //bp网络参数
params.train_method = CvANN_MLP_TrainParams::BACKPROP; //使用简单的BP算法,还可使用RPROP
/*BACKPROP表示使用back-propagation的训练方法,使用BACKPROP有两个相关参数:bp_dw-scale,bp_moment_scale
RPROP即最简单的propagation训练方法,使用PRPOP有四个相关参数:rp_dw0,rp-dw_plus,rp_dw_minus,rp_dw_min,rp_dw_max
一个是权值更新率bp_dw_scale和权值更新冲量bp_moment_scale。
这两个量一般情况设置为0.1就行了;太小了网络收敛速度会很慢,太大了可能会让网络越过最小值点
*/
params.bp_dw_scale = 0.1;
params.bp_moment_scale = 0.1;
//params.term_crit=cvTermCriteria(CV_TerMCrIT_ITER+CV_TERMCRIT_EPS,5000,0.01);
/*设置网络层数,训练数据*/
Mat layerSizes = (Mat_<int>(1, 3) << 3,18,3);//含有两个隐含层的网络结构,输入、输出层各3个节点
/*layerSizes设置了有两个隐含层的网络结构:输入层,两个隐含层,输出层。输入层和输出层节点数均为2,中间隐含层每层有两个节点
create第二个参数可以设置每个神经节点的激活函数,默认为CvANN_MLP::SIGMOID_SYM
*/
bp.create(layerSizes, CvANN_MLP::SIGMOID_SYM);//激活函数为SIGMOID函数,还可使用高斯函数(CvANN_MLP::GAUSSIAN),阶跃函数(CvANN_MLP::IDENTITY)
bp.train(trainingDataMat, labelsMat, Mat(), Mat(), params);//训练的接口train()
bp.save("bp.xml");//存储模型
cout << "训练完成!"<<endl;
delete [] IN_data;
delete [] OUT_data;
}
else
{
bp.load("bp.xml");//读取模型
cout << "下载完成!开始测试!"<<endl;
// /*使用训练好的网络结构分类新的数据*/
Mat sampleMat(1, 3,CV_32FC1); //一组测试数据
Mat stdresultMat(1, 3,CV_32FC1); //一组测试数据
Mat responseMat; //一组预测结果
fstream ifile2,ofile;
ifile2.open(testfile,ios::in);
ofile.open(resultfile,ios::out);
float err[3] = {0,0,0};
float errerr = 0;
float sum_err = 0.0;
//100组数据测试
for(int i=0;i<100;i++)
{
//每行格式 ,101,22,333,120,333,12,
ifile2.getline(buffer, 50, '\n'); //getline(char *,int,char) 表示该行字符达到 50 个或遇到换行就结束;
int num1_count = -1;
for(int j = 0; j < 50; ){
if(num1_count >=5 )
break;
else if(buffer[j] == ','){
num1_count++;
if(num1_count < 3){
if(buffer[j+4] == ','){
sampleMat.at<float>(0,num1_count) = (buffer[j+1] - '0')*100 + (buffer[j+2] - '0')*10 + (buffer[j+3] - '0')*1;
j = j+4;
}
else if (buffer[j+3] == ','){
sampleMat.at<float>(0,num1_count) = (buffer[j+1] - '0')*10 + (buffer[j+2] - '0')*1;
j = j+3;
}
else if(buffer[j+2] == ','){
sampleMat.at<float>(0,num1_count) = (buffer[j+1] - '0')*1;
j = j+2;
}
else
break;
}
else {
if(buffer[j+4] == ','){
stdresultMat.at<float>(0,num1_count-3) = (buffer[j+1] - '0')*100 + (buffer[j+2] - '0')*10 + (buffer[j+3] - '0')*1;
j = j+4;
}
else if (buffer[j+3] == ','){
stdresultMat.at<float>(0,num1_count-3) = (buffer[j+1] - '0')*10 + (buffer[j+2] - '0')*1;
j = j+3;
}
else if(buffer[j+2] == ','){
stdresultMat.at<float>(0,num1_count-3) = (buffer[j+1] - '0')*1;
j = j+2;
}
else
break;
}
}
}
cout<< sampleMat << " "<< stdresultMat <<endl;
bp.predict(sampleMat, responseMat);
cout<< responseMat <<endl;
cout << "第 " << i << " 组:误差:";
for(int k=0;k<3;k++){
err[k] = responseMat.at<float>(0,k) - stdresultMat.at<float>(0,k);
ofile << responseMat.at<float>(0,k) <<","<<stdresultMat.at<float>(0,k)<<";";
cout <<err[k]<< " ";
errerr += err[k]*err[k]; //方差
errerr /= 3;
}
cout <<" "<<errerr<<endl;
ofile << endl;
sum_err += errerr;
errerr = 0.0;
}
cout << "sum_err = "<<sum_err <<" arr_err ="<<sum_err/100<<endl;
ofile.close();
ifile2.close();
}
return 0;
}
//cmd :
//g++ `pkg-config --cflags opencv` -o Cv_NNapi Cv_NNapi.cpp `pkg-config --libs opencv`
BP神经网络参考的这个文章!
也附上参考该文然后推导一遍后写的bp神经网络吧,用的鸢尾花数据集。测试好后,我是移植到了stm32f405上,进行其他的训练,但找不到源文件了。区别是利用了硬件上的dsp模块进行加速运算,也就是把运算符改成其dsp等效的运算式。
源代码(c code)
源文件:
#include "DL_bpnet.h"
#include "sample.h"
double _m_w_rate ;//输入层-隐含层权值学习率
double _o_w_rate ;//隐含层-网络层权学习率
double _m_t_rate ;//输入层-隐含层阈值学习率
double _o_t_rate ;//隐含层-网络层阈值学习率
int _it_nums ;//迭代次数
double _err ;//误差限
double _m_threshold[MidLayerNodesNum];//输入层-隐含层阈值
double _o_threshold[OutLayerNodesNum];//隐含层-网络层阈值
double _m_weight[InLayerNodesNum][MidLayerNodesNum];//输入层-隐含层权值
double _o_weight[MidLayerNodesNum][OutLayerNodesNum];//隐含层-网络层权值
double _mint, _maxt;
const int N = 120;
const int n = 30;
double a[N][InLayerNodesNum];
double a2[n][InLayerNodesNum];
double b[N][OutLayerNodesNum];
double b2[n][OutLayerNodesNum];
double result[1][1];
void DL_bpnet(void)
{
int i_1 = 0, i_2 = 0;
for (int count = 1; count < 4; count++)
{
for (int i = 50 * (count - 1); i < (50 * count - 10); i++) {
for (int j = 0; j < 4; j++) {
a[i_1][j] = Iris_Flowers[i][j];
}
b[i_1][0] = Iris_Flowers[i][4];
i_1++;
}
for (int i = (50 * count - 10); i < 50 * count; i++) {
for (int j = 0; j < 4; j++) {
a2[i_2][j] = Iris_Flowers[i][j];
}
i_2++;
}
}
double test[1][4] = { 6.4, 2.9, 5.9, 1.8 }; //6.5, 3.0, 5.2, 2.0
BpNet_Init();
BPtrain(a, b, N);
BPsim(a2, b2, n);
BPsim(test, result, 1);
}
void normalize(double **t, int size)
{//归一化处理
_mint = 0x7FFFFFFF;//这里利用了“魔数”进行处理,不是最好的方法
_maxt = -0x7FFFFFFF;
//找到最大和最小值
for (int i = 0; i<size; ++i)
{
for (int j = 0; j<OutLayerNodesNum; ++j)
{
if (_mint>t[i][j])
_mint = t[i][j];
if (_maxt<t[i][j])
_maxt = t[i][j];
}
}
if (_mint == _maxt)
_mint = 0;
double range = _maxt - _mint;
//归一化
//找到最大和最小值
for (int i = 0; i<size; ++i)
{
for (int j = 0; j < OutLayerNodesNum; ++j)
t[i][j] = (t[i][j] - _mint) / range;
}
}
void unnormalize(double **t, int size)
{//反归一化处理
double range = _maxt - _mint;
//归一化
//找到最大和最小值
for (int i = 0; i<size; ++i)
{
for (int j = 0; j<OutLayerNodesNum; ++j)
t[i][j] = t[i][j] * range + _mint;
}
}
void BpNet_Init(void)
{
_m_w_rate = 0.5;//输入层-隐含层权值学习率
_o_w_rate = 0.5;//隐含层-网络层权值学习率
_m_t_rate = 0.5;//输入层-隐含层权值学习率
_o_t_rate = 0.5;//隐含层-网络层权值学习率
_it_nums = 10000;//迭代次数
_err = 0.01;//误差限 0.00001
Get_Random(500);
srand(rand_seed); //初始化成随机数
for (int i = 0; i<InLayerNodesNum; ++i)
{
for (int j = 0; j<MidLayerNodesNum; ++j)
_m_weight[i][j] = (2.0*(double)rand() / RAND_MAX) - 1;
}
for (int i = 0; i<MidLayerNodesNum; ++i)
{
for (int j = 0; j<OutLayerNodesNum; ++j)
_o_weight[i][j] = (2.0*(double)rand() / RAND_MAX) - 1;
}
for (int i = 0; i<MidLayerNodesNum; ++i)
_m_threshold[i] = (2.0*(double)rand() / RAND_MAX) - 1;
for (int i = 0; i<OutLayerNodesNum; ++i)
_o_threshold[i] = (2.0*(double)rand() / RAND_MAX) - 1;
}
int BPtrain(double(*p)[InLayerNodesNum],double(*t)[OutLayerNodesNum], int size)
{//
double o1[MidLayerNodesNum];
double o2[OutLayerNodesNum];
double error1[MidLayerNodesNum];
double error2[OutLayerNodesNum];
double max_error = 0;//记录最大误差
//动态内存
double *perr = (double *)malloc(size * sizeof(double));
double **t_temp = (double **)malloc(size * sizeof(double));
for (int i = 0; i<size; ++i)
{
t_temp[i] = (double *)malloc(OutLayerNodesNum * sizeof(double));
for (int j = 0; j < OutLayerNodesNum; ++j)
t_temp[i][j] = t[i][j];
}
normalize(t_temp, size);//归一化处理
int i = 0;
for (; i<_it_nums; ++i)
{
for (int j = 0; j<size; ++j)
{
//正向传播
//计算隐含层输出
for (int k = 0; k<MidLayerNodesNum; ++k)
{
o1[k] = _m_weight[0][k] * p[j][0];
for (int m = 1; m<InLayerNodesNum; ++m)
o1[k] += _m_weight[m][k] * p[j][m];
//激励函数
o1[k] = 1.0 / (1 + exp(-o1[k] - _m_threshold[k]));//隐含层各单元的输出
}
//计算输出层输出
for (int k = 0; k<OutLayerNodesNum; ++k)
{
o2[k] = _o_weight[0][k] * o1[0];
for (int m = 1; m<MidLayerNodesNum; ++m)
o2[k] += _o_weight[m][k] * o1[m];
//激励函数
o2[k] = 1.0 / (1 + exp(-o2[k] - _o_threshold[k]));//隐含层各单元的输出
}
//反向传播
for (int k = 0; k<OutLayerNodesNum; ++k)
{
//计算输出层误差
error2[k] = (t_temp[j][k] - o2[k])*o2[k] * (1 - o2[k]);
//调整权值
for (int m = 0; m<MidLayerNodesNum; ++m)
_o_weight[m][k] += _o_w_rate * error2[k] * o1[m];
}
for (int k = 0; k<MidLayerNodesNum; ++k)
{
//计算隐含层误差
double d = 0;
for (int m = 0; m<OutLayerNodesNum; ++m)
d += error2[m] * _o_weight[k][m];
//调整权值
error1[k] = d * o1[k] * (1 - o1[k]);
for (int m = 0; m<InLayerNodesNum; ++m)
_m_weight[m][k] += _m_w_rate * error1[k] * p[j][m];
}
double e = 0;
for (int k = 0; k<OutLayerNodesNum; ++k)
e += fabs(t_temp[j][k] - o2[k])*fabs(t_temp[j][k] - o2[k]);
perr[j] = e / 2;
//更新阈值
for (int k = 0; k<OutLayerNodesNum; k++)
_o_threshold[k] += _o_t_rate * error2[k]; //下一次的隐含层和输出层之间的新阈值
for (int k = 0; k<MidLayerNodesNum; k++)
_m_threshold[k] += _m_t_rate * error1[k]; //下一次的输入层和隐含层之间的新阈值
}
max_error = perr[0];
for (int j = 1; j<size; ++j)
if (perr[j]>max_error)
max_error = perr[j];
if (max_error<_err)
{
// printf("次数:%d\n",i);
break;
}
}
free(perr);
for (int i = 0; i < size; ++i)
free(t_temp[i]);
free(t_temp); //内存释放
if (i >= _it_nums)
return 0;
return 1;
}
int BPsim(double(*p)[InLayerNodesNum], double(*t)[OutLayerNodesNum], int size)
{//
double o1[MidLayerNodesNum];
double **t_temp = (double **)malloc(size * sizeof(double));//保存t的数据
for (int i = 0; i < size; ++i)
t_temp[i] = (double *)malloc(OutLayerNodesNum * sizeof(double));
for (int i = 0; i < size; ++i)
{
//正向传播
//计算隐含层输出
for (int k = 0; k < MidLayerNodesNum; ++k)
{
o1[k] = _m_weight[0][k] * p[i][0];
for (int m = 1; m < InLayerNodesNum; ++m)
o1[k] += _m_weight[m][k] * p[i][m];
o1[k] = 1.0 / (1.0 + exp(-o1[k] - _m_threshold[k]));//隐含层各单元的输出
}
//计算输出层输出
for (int k = 0; k < OutLayerNodesNum; ++k)
{
t_temp[i][k] = _o_weight[0][k] * o1[0];
for (int m = 1; m < MidLayerNodesNum; ++m)
t_temp[i][k] += _o_weight[m][k] * o1[m];
t_temp[i][k] = 1.0 / (1.0 + exp(-t_temp[i][k] - _o_threshold[k]));//隐含层各单元的输出
}
}
unnormalize(t_temp, size); //反归一化
for (int i = 0; i<size; ++i)
for (int j = 0; j < OutLayerNodesNum; ++j) {
t[i][j] = t_temp[i][j];
}
for (int i = 0; i < size; ++i)
free(t_temp[i]);
free(t_temp); //内存释放
return 1;
}
头文件:
/**
******************************************************************************
* File Name : DL_bpbet.h
* Description : use bpnet to train and sim
* Data : 2018/4/23
* Hardward :
* Important functions { BpNet_Init()
BPtrain(a,b,c) "a" defined a[训练/预测数据数量][输入维度]
BPsim(a,b,c) "b" defined b[训练/预测数据数量][输出维度]
"c" 训练/预测数据数量
}
*****************************************************************************
*/
//#include <ctime>
//#include <cmath>
//#include <cstdlib>
//#include <time.h>
#include <math.h>
#include <stdlib.h>
#include <stdio.h>
#include "HW_adc.h" //用来采集随机数
#include "arm_math.h" //dsp
extern int rand_seed; //产生的随机值
#define InLayerNodesNum 4//输入层节点数
#define MidLayerNodesNum 6//隐层节点数
#define OutLayerNodesNum 1
void normalize(double **t, int size);
void unnormalize(double **t, int size);
void BpNet_Init(void);
int BPtrain(double(*p)[InLayerNodesNum], double(*t)[OutLayerNodesNum], int size);
int BPsim(double(*p)[InLayerNodesNum], double(*t)[OutLayerNodesNum], int size);
测试集文件(鸢尾花数据集)
#define Iris 1
#define setosa 0.9,
#define versicolor 0.8,
#define virginica 0.7,
double Iris_Flowers[150][5] =
{
5.1, 3.5, 1.4, 0.2, Iris - setosa
4.9, 3.0, 1.4, 0.2, Iris - setosa
4.7, 3.2, 1.3, 0.2, Iris - setosa
4.6, 3.1, 1.5, 0.2, Iris - setosa
5.0, 3.6, 1.4, 0.2, Iris - setosa
5.4, 3.9, 1.7, 0.4, Iris - setosa
4.6, 3.4, 1.4, 0.3, Iris - setosa
5.0, 3.4, 1.5, 0.2, Iris - setosa
4.4, 2.9, 1.4, 0.2, Iris - setosa
4.9, 3.1, 1.5, 0.1, Iris - setosa
5.4, 3.7, 1.5, 0.2, Iris - setosa
4.8, 3.4, 1.6, 0.2, Iris - setosa
4.8, 3.0, 1.4, 0.1, Iris - setosa
4.3, 3.0, 1.1, 0.1, Iris - setosa
5.8, 4.0, 1.2, 0.2, Iris - setosa
5.7, 4.4, 1.5, 0.4, Iris - setosa
5.4, 3.9, 1.3, 0.4, Iris - setosa
5.1, 3.5, 1.4, 0.3, Iris - setosa
5.7, 3.8, 1.7, 0.3, Iris - setosa
5.1, 3.8, 1.5, 0.3, Iris - setosa
5.4, 3.4, 1.7, 0.2, Iris - setosa
5.1, 3.7, 1.5, 0.4, Iris - setosa
4.6, 3.6, 1.0, 0.2, Iris - setosa
5.1, 3.3, 1.7, 0.5, Iris - setosa
4.8, 3.4, 1.9, 0.2, Iris - setosa
5.0, 3.0, 1.6, 0.2, Iris - setosa
5.0, 3.4, 1.6, 0.4, Iris - setosa
5.2, 3.5, 1.5, 0.2, Iris - setosa
5.2, 3.4, 1.4, 0.2, Iris - setosa
4.7, 3.2, 1.6, 0.2, Iris - setosa
4.8, 3.1, 1.6, 0.2, Iris - setosa
5.4, 3.4, 1.5, 0.4, Iris - setosa
5.2, 4.1, 1.5, 0.1, Iris - setosa
5.5, 4.2, 1.4, 0.2, Iris - setosa
4.9, 3.1, 1.5, 0.1, Iris - setosa
5.0, 3.2, 1.2, 0.2, Iris - setosa
5.5, 3.5, 1.3, 0.2, Iris - setosa
4.9, 3.1, 1.5, 0.1, Iris - setosa
4.4, 3.0, 1.3, 0.2, Iris - setosa
5.1, 3.4, 1.5, 0.2, Iris - setosa
5.0, 3.5, 1.3, 0.3, Iris - setosa
4.5, 2.3, 1.3, 0.3, Iris - setosa
4.4, 3.2, 1.3, 0.2, Iris - setosa
5.0, 3.5, 1.6, 0.6, Iris - setosa
5.1, 3.8, 1.9, 0.4, Iris - setosa
4.8, 3.0, 1.4, 0.3, Iris - setosa
5.1, 3.8, 1.6, 0.2, Iris - setosa
4.6, 3.2, 1.4, 0.2, Iris - setosa
5.3, 3.7, 1.5, 0.2, Iris - setosa
5.0, 3.3, 1.4, 0.2, Iris - setosa
7.0, 3.2, 4.7, 1.4, Iris - versicolor
6.4, 3.2, 4.5, 1.5, Iris - versicolor
6.9, 3.1, 4.9, 1.5, Iris - versicolor
5.5, 2.3, 4.0, 1.3, Iris - versicolor
6.5, 2.8, 4.6, 1.5, Iris - versicolor
5.7, 2.8, 4.5, 1.3, Iris - versicolor
6.3, 3.3, 4.7, 1.6, Iris - versicolor
4.9, 2.4, 3.3, 1.0, Iris - versicolor
6.6, 2.9, 4.6, 1.3, Iris - versicolor
5.2, 2.7, 3.9, 1.4, Iris - versicolor
5.0, 2.0, 3.5, 1.0, Iris - versicolor
5.9, 3.0, 4.2, 1.5, Iris - versicolor
6.0, 2.2, 4.0, 1.0, Iris - versicolor
6.1, 2.9, 4.7, 1.4, Iris - versicolor
5.6, 2.9, 3.6, 1.3, Iris - versicolor
6.7, 3.1, 4.4, 1.4, Iris - versicolor
5.6, 3.0, 4.5, 1.5, Iris - versicolor
5.8, 2.7, 4.1, 1.0, Iris - versicolor
6.2, 2.2, 4.5, 1.5, Iris - versicolor
5.6, 2.5, 3.9, 1.1, Iris - versicolor
5.9, 3.2, 4.8, 1.8, Iris - versicolor
6.1, 2.8, 4.0, 1.3, Iris - versicolor
6.3, 2.5, 4.9, 1.5, Iris - versicolor
6.1, 2.8, 4.7, 1.2, Iris - versicolor
6.4, 2.9, 4.3, 1.3, Iris - versicolor
6.6, 3.0, 4.4, 1.4, Iris - versicolor
6.8, 2.8, 4.8, 1.4, Iris - versicolor
6.7, 3.0, 5.0, 1.7, Iris - versicolor
6.0, 2.9, 4.5, 1.5, Iris - versicolor
5.7, 2.6, 3.5, 1.0, Iris - versicolor
5.5, 2.4, 3.8, 1.1, Iris - versicolor
5.5, 2.4, 3.7, 1.0, Iris - versicolor
5.8, 2.7, 3.9, 1.2, Iris - versicolor
6.0, 2.7, 5.1, 1.6, Iris - versicolor
5.4, 3.0, 4.5, 1.5, Iris - versicolor
6.0, 3.4, 4.5, 1.6, Iris - versicolor
6.7, 3.1, 4.7, 1.5, Iris - versicolor
6.3, 2.3, 4.4, 1.3, Iris - versicolor
5.6, 3.0, 4.1, 1.3, Iris - versicolor
5.5, 2.5, 4.0, 1.3, Iris - versicolor
5.5, 2.6, 4.4, 1.2, Iris - versicolor
6.1, 3.0, 4.6, 1.4, Iris - versicolor
5.8, 2.6, 4.0, 1.2, Iris - versicolor
5.0, 2.3, 3.3, 1.0, Iris - versicolor
5.6, 2.7, 4.2, 1.3, Iris - versicolor
5.7, 3.0, 4.2, 1.2, Iris - versicolor
5.7, 2.9, 4.2, 1.3, Iris - versicolor
6.2, 2.9, 4.3, 1.3, Iris - versicolor
5.1, 2.5, 3.0, 1.1, Iris - versicolor
5.7, 2.8, 4.1, 1.3, Iris - versicolor
6.3, 3.3, 6.0, 2.5, Iris - virginica
5.8, 2.7, 5.1, 1.9, Iris - virginica
7.1, 3.0, 5.9, 2.1, Iris - virginica
6.3, 2.9, 5.6, 1.8, Iris - virginica
6.5, 3.0, 5.8, 2.2, Iris - virginica
7.6, 3.0, 6.6, 2.1, Iris - virginica
4.9, 2.5, 4.5, 1.7, Iris - virginica
7.3, 2.9, 6.3, 1.8, Iris - virginica
6.7, 2.5, 5.8, 1.8, Iris - virginica
7.2, 3.6, 6.1, 2.5, Iris - virginica
6.5, 3.2, 5.1, 2.0, Iris - virginica
6.4, 2.7, 5.3, 1.9, Iris - virginica
6.8, 3.0, 5.5, 2.1, Iris - virginica
5.7, 2.5, 5.0, 2.0, Iris - virginica
5.8, 2.8, 5.1, 2.4, Iris - virginica
6.4, 3.2, 5.3, 2.3, Iris - virginica
6.5, 3.0, 5.5, 1.8, Iris - virginica
7.7, 3.8, 6.7, 2.2, Iris - virginica
7.7, 2.6, 6.9, 2.3, Iris - virginica
6.0, 2.2, 5.0, 1.5, Iris - virginica
6.9, 3.2, 5.7, 2.3, Iris - virginica
5.6, 2.8, 4.9, 2.0, Iris - virginica
7.7, 2.8, 6.7, 2.0, Iris - virginica
6.3, 2.7, 4.9, 1.8, Iris - virginica
6.7, 3.3, 5.7, 2.1, Iris - virginica
7.2, 3.2, 6.0, 1.8, Iris - virginica
6.2, 2.8, 4.8, 1.8, Iris - virginica
6.1, 3.0, 4.9, 1.8, Iris - virginica
6.4, 2.8, 5.6, 2.1, Iris - virginica
7.2, 3.0, 5.8, 1.6, Iris - virginica
7.4, 2.8, 6.1, 1.9, Iris - virginica
7.9, 3.8, 6.4, 2.0, Iris - virginica
6.4, 2.8, 5.6, 2.2, Iris - virginica
6.3, 2.8, 5.1, 1.5, Iris - virginica
6.1, 2.6, 5.6, 1.4, Iris - virginica
7.7, 3.0, 6.1, 2.3, Iris - virginica
6.3, 3.4, 5.6, 2.4, Iris - virginica
6.4, 3.1, 5.5, 1.8, Iris - virginica
6.0, 3.0, 4.8, 1.8, Iris - virginica
6.9, 3.1, 5.4, 2.1, Iris - virginica
6.7, 3.1, 5.6, 2.4, Iris - virginica
6.9, 3.1, 5.1, 2.3, Iris - virginica
5.8, 2.7, 5.1, 1.9, Iris - virginica
6.8, 3.2, 5.9, 2.3, Iris - virginica
6.7, 3.3, 5.7, 2.5, Iris - virginica
6.7, 3.0, 5.2, 2.3, Iris - virginica
6.3, 2.5, 5.0, 1.9, Iris - virginica
6.5, 3.0, 5.2, 2.0, Iris - virginica
6.2, 3.4, 5.4, 2.3, Iris - virginica
5.9, 3.0, 5.1, 1.8, Iris - virginica
};
更多推荐
所有评论(0)