Подтвердить что ты не робот

Чтение/запись данных в формате libsvm

Как мне читать/записывать libsvm данные в/из R?

Формат libsvm - это редкие данные, например

<class/target>[ <attribute number>:<attribute value>]*

(cf. сжатое хранилище строк (CRS)), например,

1 10:3.4 123:0.5 34567:0.231
0.2 22:1 456:03

Я уверен, что я могу что-то взбить, но я бы скорее использовал что-то с полки. Однако R library foreign, похоже, не предоставляет необходимых функций.

4b9b3361

Ответ 1

e1071 находится с полки:

install.packages("e1071")
library(e1071)
read.matrix.csr(...)
write.matrix.csr(...)

Примечание: она реализована в R, а не в C, поэтому она собака-медленная.

У него даже есть специальная виньетка Поддержка векторных машин - интерфейс для libsvm в пакете e1071.

r.vw в комплекте с vowpal_wabbit

Примечание: она реализована в R, а не в C, поэтому она собака-медленная.

Ответ 2

Я выполняю задание с использованием решения zygmuntz на наборе данных с 25k наблюдениями (строками) в течение почти 5 часов. Он сделал 3k-иш строк. Это длилось так долго, что я закодировал это тем временем (на основе кода zygmuntz):

require(Matrix)
read.libsvm = function( filename ) {
  content = readLines( filename )
  num_lines = length( content )
  tomakemat = cbind(1:num_lines, -1, substr(content,1,1))

  # loop over lines
  makemat = rbind(tomakemat,
  do.call(rbind, 
    lapply(1:num_lines, function(i){
       # split by spaces, remove lines
           line = as.vector( strsplit( content[i], ' ' )[[1]])
           cbind(i, t(simplify2array(strsplit(line[-1],
                          ':'))))   
})))
class(makemat) = "numeric"

#browser()
yx = sparseMatrix(i = makemat[,1], 
              j = makemat[,2]+2, 
          x = makemat[,3])
return( yx )
}

Это выполнялось в минутах на одном компьютере (возможно, проблемы с памятью также были решены с помощью zygmuntz). Надеюсь, это поможет любому, у кого есть такая же проблема.

Помните, если вам нужно делать большие вычисления в R, VECTORIZE!

EDIT: исправлена ​​ошибка индексации, которую я нашел сегодня утром.

Ответ 3

Я придумал свое собственное специальное решение, использующее некоторые утилиты data.table,

Он практически не работал в тестовом наборе данных, который я нашел (данные о жилье в Бостоне).

Преобразуя это в data.table (ортогонально к решению, но добавляя сюда для легкой воспроизводимости):

library(data.table)
x = fread("/media/data_drive/housing.data.fw",
          sep = "\n", header = FALSE)
#usually fixed-width conversion is harder, but everything here is numeric
columns =  c("CRIM", "ZN", "INDUS", "CHAS",
             "NOX", "RM", "AGE", "DIS", "RAD", 
             "TAX", "PTRATIO", "B", "LSTAT", "MEDV")
DT = with(x, fread(paste(gsub("\\s+", "\t", V1), collapse = "\n"),
                   header = FALSE, sep = "\t",
                   col.names = columns))

Вот он:

DT[ , fwrite(as.data.table(paste0(
  MEDV, " | ", sapply(transpose(lapply(
    names(.SD), function(jj)
      paste0(jj, ":", get(jj)))),
    paste, collapse = " "))), 
  "/path/to/output", col.names = FALSE, quote = FALSE),
  .SDcols = !"MEDV"]
#what gets sent to as.data.table:
#[1] "24 | CRIM:0.00632 ZN:18 INDUS:2.31 CHAS:0 NOX:0.538 RM:6.575 
#  AGE:65.2 DIS:4.09 RAD:1 TAX:296 PTRATIO:15.3 B:396.9 LSTAT:4.98 MEDV:24"      
#[2] "21.6 | CRIM:0.02731 ZN:0 INDUS:7.07 CHAS:0 NOX:0.469 RM:6.421 
#  AGE:78.9 DIS:4.9671 RAD:2 TAX:242 PTRATIO:17.8 B:396.9 LSTAT:9.14 MEDV:21.6"
# ...

Может быть, лучший способ понять это через fwrite, чем as.data.table, но я не могу думать об одном (до setDT работает на векторах).

Я повторил это, чтобы проверить его производительность на большем наборе данных (просто взорвите текущий набор данных):

DT2 = rbindlist(replicate(1000, DT, simplify = FALSE))

Операция была довольно быстрой по сравнению с некоторыми из приведенных здесь сообщений (я еще не потрудился сравнивать непосредственно):

system.time(.)
#    user  system elapsed 
#   8.392   0.000   8.385 

Я также тестировал, используя writeLines вместо fwrite, но последний был лучше.


Я снова смотрю и вижу, что это может занять некоторое время, чтобы понять, что происходит. Возможно, версия magrittr -piped будет проще:

DT[ , 
    #1) prepend each column values with the column name
    lapply(names(.SD), function(jj)
      paste0(jj, ":", get(jj))) %>%
      #2) transpose this list (using data.table fast tool)
      #   (was column-wise, now row-wise)
      #3) concatenate columns, separated by " "
      transpose %>% sapply(paste, collapse = " ") %>%
      #4) prepend each row with the target value
      #   (with Vowpal Wabbit in mind, separate with a pipe)
      paste0(MEDV, " | ", .) %>%
      #5) convert this to a data.table to use fwrite
      as.data.table %>%
      #6) fwrite it; exclude nonsense column name,
      #   and force quotes off
      fwrite("/path/to/data", 
             col.names = FALSE, quote = FALSE),
  .SDcols = !"MEDV"]

чтение в таких файлах намного проще **

#quickly read data; don't split within lines
x = fread("/path/to/data", sep = "\n", header = FALSE)

#tstrsplit is transpose(strsplit(.))
dt1 = x[ , tstrsplit(V1, split = "[| :]+")]

#even columns have variable names
nms = c("target_name", 
        unlist(dt1[1L, seq(2L, ncol(dt1), by = 2L), 
                   with = FALSE]))

#odd columns have values
DT = dt1[ , seq(1L, ncol(dt1), by = 2L), with = FALSE]
#add meaningful names
setnames(DT, nms)

** это не будет работать с "оборванными" /разреженными входными данными. Я не думаю, что есть способ расширить это, чтобы работать в таких случаях.

Ответ 5

На основе некоторых комментариев. Я добавляю его как aswer, чтобы его легче было использовать другим. Это необходимо для записи данных в формате libsvm.

Функция для записи формата data.frame в svm light. Я добавил аргумент train = {TRUE, FALSE}, если у данных нет меток. В этом случае индекс класса игнорируется.

write.libsvm = function(data, filename= "out.dat", class = 1, train=TRUE) {
  out = file(filename)
  if(train){
    writeLines(apply(data, 1, function(X) {
      paste(X[class], 
            apply(cbind(which(X!=0)[-class], 
                        X[which(X!=0)[-class]]), 
                  1, paste, collapse=":"), 
            collapse=" ") 
      }), out)
  } else {
    # leaves 1 as default for the new data without predictions. 
    writeLines(apply(data, 1, function(X) {
      paste('1',
            apply(cbind(which(X!=0), X[which(X!=0)]), 1, paste, collapse=":"), 
            collapse=" ") 
      }), out)
  }
  close(out) 
}

** РЕДАКТИРОВАТЬ **

Другая опция - если у вас уже есть данные в объекте data.table

libfm и SVMlight имеют одинаковый формат, поэтому эта функция должна работать.

library(data.table)

data.table.fm <- function (data = X, fileName = "../out.fm", target = "y_train", 
    train = TRUE) {
    if (train) {
        if (is.logical(data[[target]]) | sum(levels(factor(data[[target]])) == 
            levels(factor(c(0, 1)))) == 2) {
            data[[target]][data[[target]] == TRUE] = 1
            data[[target]][data[[target]] == FALSE] = -1
        }
    }
    specChar = "\\(|\\)|\\||\\:"
    specCharSpace = "\\(|\\)|\\||\\:| "
    parsingNames <- function(x) {
        ret = c()
        for (el in x) ret = append(ret, gsub(specCharSpace, "_", 
            el))
        ret
    }
    parsingVar <- function(x, keepSpace, hard_parse) {
        if (!keepSpace) 
            spch = specCharSpace
        else spch = specChar
        if (hard_parse) 
            gsub("(^_( *|_*)+)|(^_$)|(( *|_*)+_$)|( +_+ +)", 
                " ", gsub(specChar, "_", gsub("(^ +)|( +$)", 
                  "", x)))
        else gsub(spch, "_", x)
    }
    setnames(data, names(data), parsingNames(names(data)))
    target = parsingNames(target)
    format_vw <- function(column, formater) {
        ifelse(as.logical(column), sprintf(formater, j, column), 
            "")
    }
    all_vars = names(data)[!names(data) %in% target]
    cat("Reordering data.table if class isn't first\n")
    target_inx = which(names(data) %in% target)
    rest_inx = which(!names(data) %in% target)
    cat("Adding Variable names to data.table\n")
    for (j in rest_inx) {
        column = data[[j]]
        formater = "%s:%f"
        set(data, i = NULL, j = j, value = format_vw(column, 
            formater))
        cat(sprintf("Fixing %s\n", j))
    }
    data = data[, c(target_inx, rest_inx), with = FALSE]
    drop_extra_space <- function(x) {
        gsub(" {1,}", " ", x)
    }
    cat("Pasting data - Removing extra spaces\n")
    data = apply(data, 1, function(x) drop_extra_space(paste(x, 
        collapse = " ")))
    cat("Writing to disk\n")
    write.table(data, file = fileName, sep = " ", row.names = FALSE, 
        col.names = FALSE, quote = FALSE)
}

Ответ 6

Я пошел с решением с двумя переходами: сначала конвертируйте данные R в другой формат, а затем в LIBSVM:

  • Использованный R-пакет, используемый для преобразования (и записи) кадра данных в формат ARFF (изменение write.arff смены write.table на na = "0.0" вместо na = "?", в противном случае шаг 2 не работает)
  • Используется https://github.com/dat/svm-tools/blob/master/arff2svm.py для преобразования формата ARFF в LIBSVM

Мой набор данных 200K x 500, и это заняло всего 3-5 минут.