首页 > 代码库 > WEKA学习——CSVLoader 实例训练 和 源码分析

WEKA学习——CSVLoader 实例训练 和 源码分析

简介:

Weka支持多种数据导入方式,CSVLoader是能从csv文件加载数据集,也可以保存为arff格式文件。官方介绍文件:Converting CSV to ARFF (
http://weka.wikispaces.com/Converting+CSV+to+ARFF)

CSVLoader加载文件,关键是对文件字段属性名称和属性的类型需要自己定义,这样才能得到满足自己需要的数据集。
CSVLoader通过options设置,可以设置每一列的属性为Nominal,String,Date类型。如"-N 1-2 -S 3 -D 4" Options 就是将属性1-2设置为Nominal类型,属性3设置为String类型,属性4设置为Date类型。

实例训练:

  • 准备数据

(第一个字段为序列号,第二个字段为分词后的结果,格式为csv格式,utf-8编码):
  1. 110112006582760,修理 水泵 安装 制冷 设备 工程 技术研究 试验 发展 技术开发
  2. 110108003557082,销售 计算机 软件 辅助 设备 电子产品 取得 行政许可 项目 除外
  3. 110107000885559,技术转让 销售 百货 针纺织品 五金 交电 化工 建筑材料 机械设备 电器设备
  4. 110109002641736,汽车配件 计算机 软硬件 外围设备 家居装饰 设计 制作 服装 计算机 软硬件
  5. 110102000765431,技术开发 动力 技术开发 咨询 销售 机械 电器设备 发电机组 五金交电 橡胶制品
  6. 110109004903736,建筑材料 金属材料 黄金 化工产品 不含 化学 危险品 一类 易制毒 化学品
  7. 110108003533570,计算机 软硬件 外设 数码 技术开发 技术开发 转让 咨询 服务 培训 技术推广 服务 销售
  8. 110101000171791,软件 技术开发 技术咨询 技术培训 技术转让 技术服务 信息 咨询
  9. 110108000938562,不含 中介 服务 劳务 服务 销售 五金交电 电子计算机 百货 汽车配件
 

  • 目的:

1. 转换为想要的arff格式文件,并保存
2. 利用Filter中的StringToWordVector对其进行过滤,方便后面根据TFIDF对文件进行分类聚类。

  • 实验代码:

  1. public static void main(String[] args) throws Exception {
  2. String filename = "datasets/companies.csv";
  3. String savearff = "datasets/companies.arff";
  4. CSVLoader loader = new CSVLoader();
  5. loader.setSource(new File(filename));
  6. // 在这里才能设置你读取的那个字段是String,而不是nominal
  7. loader.setStringAttributes("2"); // from 1
  8. loader.setNominalAttributes("1");
  9. Instances datasrc = loader.getDataSet();
  10.                 datasrc.renameAttribute(0, "regId");// rename attribu
  11. datasrc.renameAttribute(1, "text");
  12. datasrc.setClassIndex(0);
  13. // dataRaw.setRelationName(newName); //这里可以设置relationName
  14. //这里可以输出读取后Instances的结构信息,当然自己还可以数去其他信息
  15. //System.out.println(datasrc.stringFreeStructure());
  16. // save ARFF
  17. ArffSaver saver = new ArffSaver();
  18. saver.setInstances(datasrc);
  19. saver.setFile(new File(savearff));
  20. // saver.setDestination(new File(args[1]));
  21. saver.writeBatch();
  22. }
 这里读取到的Instances和保存的Instances都是一致的,数据文件如下:
  1. @relation companies
  2. @attribute regId {1.10108003557082E14,1.10107000885559E14,1.10109002641736E14,1.10102000765431E14,1.10109004903736E14,1.1010800353357E14,1.10101000171791E14,1.10108000938562E14}
  3. @attribute text string
  4. @data
  5. 1.10108003557082E14,‘销售 计算机 软件 及 辅助 设备 电子产品 未 取得 行政许可 的 项目 除外 ‘
  6. 1.10107000885559E14,‘技术转让 销售 百货 针纺织品 五金 交电 化工 建筑材料 机械设备 电器设备 ‘
  7. 1.10109002641736E14,‘汽车配件 计算机 软硬件 及 外围设备 家居装饰 设计 制作 服装 计算机 软硬件 ‘
  8. 1.10102000765431E14,‘技术开发 动力 技术开发 咨询 销售 机械 电器设备 发电机组 五金交电 橡胶制品 ‘
  9. 1.10109004903736E14,‘建筑材料 金属材料 除 黄金 化工产品 不含 化学 危险品 及 一类 易制毒 化学品 ‘
  10. 1.1010800353357E14,‘计算机 软硬件 及 外设 数码 技术开发 技术开发 转让 咨询 服务 培训 技术推广 服务 销售‘
  11. 1.10101000171791E14,‘软件 技术开发 技术咨询 技术培训 技术转让 技术服务 信息 咨询 ‘
  12. 1.10108000938562E14,‘不含 中介 服务 劳务 服务 销售 五金交电 电子计算机 百货 汽车配件 ‘
这里需要注意两个地方:
1. regId 为nominal,所以attribute后面列出了所有可能值。但是原数据id 110112006582760比较大,采用了科学技术法来保存double数据,所以出现了以下情况。但是这样并不影响数据分析和后续处理,两个不同的id数字,他们的科学表示法一定不一样。(具体原因和解决办法,见下面的源码分析
2.第二个字段设置名字为text,类型为string。 对于其他date日期类型,自己也可以根据CSVLoader的setDateAttributes(String value)和setDateFormat(String value);两个方法来设定。

对于第一个注意的地方,我们可以通过对源码进行修改,然后得到修正。
 

CSVLoader源码分析:

CSVLoader代码加载文件的流程主要在getDataSet()函数里面,一般是先设置参数,然后执行getDataSet()函数。
CSVLoader首先会判断参数设置,根据首行数据判断每个字段的类型。然后执行getInstance(StreamTokenizer tokenizer) 函数一行行加载数据。下面只对getInstance函数进行简要分析:

/**
 * Attempts to parse a line of the data set.
 * 这个类是对每一行进行解析,首先看每个字段是否转换为double,否则转换为string 
 * 注意其中注释部分 // Ma: 
 */  
    private FastVector getInstance(StreamTokenizer tokenizer) throws IOException {
        FastVector current = new FastVector();
        // Check if end of file reached.
        ConverterUtils.getFirstToken(tokenizer);
        if (tokenizer.ttype == StreamTokenizer.TT_EOF) {
            return null;
        }
        boolean first = true;
        boolean wasSep;
        while (tokenizer.ttype != StreamTokenizer.TT_EOL && tokenizer.ttype != StreamTokenizer.TT_EOF) {
            // Get next token
            if (!first) {
                ConverterUtils.getToken(tokenizer);
            }
            if (tokenizer.ttype == ‘,‘ || tokenizer.ttype == ‘\t‘ || tokenizer.ttype == StreamTokenizer.TT_EOL) {
                current.addElement(m_MissingValue);
                wasSep = true;
            } else {
                wasSep = false;
                if (tokenizer.sval.equals(m_MissingValue)) {
                    current.addElement(new String(m_MissingValue));
                } else {
                    // try to parse as a number
                    try {
                        // Ma:
                        // 注意这行代码总会将“大数”进行科学技术法表示,
                        // 如:110112006582760表示成:1.10108003557082E14。
                        // Ma:但有时我们的"大数"一般指注册号什么的,应该是nominal,没有大小之分
                        // 所以这里可以修改,让Double完整显示

                        //double val = Double.valueOf(tokenizer.sval).doubleValue(); //注释掉
                        //current.addElement(new Double(val));   // 注释掉
                        // 按如下修改,会把所有的numeric字段修改为nominal or string
                        current.addElement(new String(tokenizer.sval));  //修改添加
                    } catch (NumberFormatException e) {
                        // otherwise assume its an enumerated value
                        current.addElement(new String(tokenizer.sval));
                    }
                }
            }
            if (!wasSep) {
                ConverterUtils.getToken(tokenizer);
            }
            first = false;
        }
        // check number of values read
        if (current.size() != m_structure.numAttributes()) {
            ConverterUtils.errms(tokenizer, "wrong number of values. Read " + current.size() + ", expected "
                    + m_structure.numAttributes());
        }
        // check for structure update
        try {
            checkStructure(current);
        } catch (Exception ex) {
            ex.printStackTrace();
        }
        return current;
    }  


根据上面的源代码,对其中的阴影部分进行修改,这样CSVLoader就不会对长数字进行转换。然后import修改后的CSVLoader,在执行上面的代码,得到如下的数据结果:
  1. @relation companies
  2. @attribute regId {110108003557082,110107000885559,110109002641736,110102000765431,110109004903736,110108003533570,110101000171791,110108000938562}
  3. @attribute text string
  4. @data
  5. 110108003557082,‘销售 计算机 软件 及 辅助 设备 电子产品 未 取得 行政许可 的 项目 除外 ‘
  6. 110107000885559,‘技术转让 销售 百货 针纺织品 五金 交电 化工 建筑材料 机械设备 电器设备 ‘
  7. 110109002641736,‘汽车配件 计算机 软硬件 及 外围设备 家居装饰 设计 制作 服装 计算机 软硬件 ‘
  8. 110102000765431,‘技术开发 动力 技术开发 咨询 销售 机械 电器设备 发电机组 五金交电 橡胶制品 ‘
  9. 110109004903736,‘建筑材料 金属材料 除 黄金 化工产品 不含 化学 危险品 及 一类 易制毒 化学品 ‘
  10. 110108003533570,‘计算机 软硬件 及 外设 数码 技术开发 技术开发 转让 咨询 服务 培训 技术推广 服务 销售‘
  11. 110101000171791,‘软件 技术开发 技术咨询 技术培训 技术转让 技术服务 信息 咨询 ‘
  12. 110108000938562,‘不含 中介 服务 劳务 服务 销售 五金交电 电子计算机 百货 汽车配件 ‘
但这样会有一个问题,若数据还有其他numeric属性字段,同样也会处理成nomial。只要知道是这么部分代码其的作用,并不一定需要这样修改,这样的内部表示挺好的,只需在输出文件时定义显示格式就好了。如:
  1. DecimalFormat df = new DecimalFormat("0.0");//保留一位小数
  2. String str="1.10108003557082E14";
  3. double val=Double.valueOf(str);
  4. System.out.println(val); //默认科学表示法输出
  5. System.out.println(df.format(val));//采用指定格式输出
Weka内部为什么没有这么做,可能是因为他不知道具体要保存什么的格式,就以科学计数法给保存了。
这里还有一个简单的方法,若里面还有其他numerical字段,可以将想要处理为nominal的数字字段,前面加上几个字符,对于本例可以将第一个序列号的字段前面都加id(这样就会抛出NumberFormatException),得到如下数据格式,这样CSVLoader就能正常处理,你也能得到你想要的结果了。
  1. @relation companies
  2. @attribute regId {id110108003557082,id110107000885559,id110109002641736,id110102000765431,id110109004903736,id110108003533570,id110101000171791,id110108000938562}
  3. @attribute text string
  4. @data
  5. id110108003557082,‘销售 计算机 软件 及 辅助 设备 电子产品 未 取得 行政许可 的 项目 除外 ‘
  6. id110107000885559,‘技术转让 销售 百货 针纺织品 五金 交电 化工 建筑材料 机械设备 电器设备 ‘
  7. id110109002641736,‘汽车配件 计算机 软硬件 及 外围设备 家居装饰 设计 制作 服装 计算机 软硬件 ‘
  8. id110102000765431,‘技术开发 动力 技术开发 咨询 销售 机械 电器设备 发电机组 五金交电 橡胶制品 ‘
  9. id110109004903736,‘建筑材料 金属材料 除 黄金 化工产品 不含 化学 危险品 及 一类 易制毒 化学品 ‘
  10. id110108003533570,‘计算机 软硬件 及 外设 数码 技术开发 技术开发 转让 咨询 服务 培训 技术推广 服务 销售‘
  11. id110101000171791,‘软件 技术开发 技术咨询 技术培训 技术转让 技术服务 信息 咨询 ‘
  12. id110108000938562,‘不含 中介 服务 劳务 服务 销售 五金交电 电子计算机 百货 汽车配件 ‘

拓展知识:

  • Instances实例集 Attributes类型修改

对已经处理好的Instances进行属性类型的改变(通过Filter),如下面例子:
import weka.filters.unsupervised.attribute.NumericToNominal;  
NumericToNominal num2Nominal=new NumericToNominal();
num2Nominal.setInputFormat(datasrc);
num2Nominal.setAttributeIndices("1");  
Instances dataFiltered = Filter.useFilter(datasrc, filter);  
当然还有NominalToString这样的Filter类,用法类似。

  • StringToWordVector使用

利用StringToWordVector可以计算每个文本的TF or TFIDF值,然后在计算不同文本见的(余弦orJacobi)相似性。最后可以用来文本的分类聚类。
  1. /* ......紧接最上面的代码 */
  2. /* filter 分词后的 string */
  3. StringToWordVector filter = new StringToWordVector();
  4. filter.setInputFormat(datasrc);
  5. // String stopwordfile = "datasets/stopwords.en";
  6. // filter.setStopwords(new File(stopwordfile));
  7. // filter.setUseStoplist(true);
  8. String optionStr = "-R first-last -W 1000 -prune-rate -1.0 -C -I -N 1";
  9. filter.setOptions(Utils.splitOptions(optionStr));
  10. Instances dataFiltered = Filter.useFilter(datasrc, filter);
  11. //....接下来可以对dataFilered数据集进行分类或聚类

 


WEKA学习——CSVLoader 实例训练 和 源码分析