最近在做基于内容的推荐,试了几种方法:向量空间模型(用lucene实现)、贝叶斯分类、聚类(用weka的SimpleKMeans)。
用聚类做推荐,我的思路是首先将所有文本进行聚类,如果一类中有用户读过的书,则将这类中他没有读过的书推荐给他。
文本聚类实现是用的weka,首先用StringToWordVector过滤器,将文本转化为向量,然后用SimpleKMeans进行聚类。但聚类的效果不太理想,经常有一类包含50%以上的数据。虽然对这一类多聚几次也能把数据分开。
向量的维度选择很重要,也就是选那些词做为基本的维度。这一步是用StringToWordVector实现的,这个过滤器,选取一些词,然后将文本转化为向量。我以为选取的过程是用tf-idf算的,也就是保留tf-idf较大的前多少个词。查看源码后发现不是这样的。
下面这个方法就是选取词典的方法:
/**
* determines the dictionary.
*/
private void determineDictionary() {
//太长了,省略掉,大家去看源码
}
有个内部类,用于统计每个词的词频和文档频率:
/**
* Used to store word counts for dictionary selection *based on a threshold.
*/
private class Count implements Serializable, RevisionHandler {
/** for serialization. */
static final long serialVersionUID = 2157223818584474321L;
/** the counts. */
public int count, docCount;
/**
* the constructor.
*
* @param c the count
*/
public Count(int c) {
count = c;
}
}
选取词典的大概步骤如下:
1、首先分词,然后统计每个词的词频和文档频率,存储在TreeMap dictionaryArr[0]中,key是词,value是Count,其中有词频和文档频率;
2、然后将所有词频存于一个int array[]中,然后非降序排序。
3、然后根据要的词数和总次数,确定一个值int prune[0],下面要选取的词的词频必须大于等于这个值。
所以字典不是根据tf*idf的值选取的,而是根据tf。所以如果想要根据tf-idf选取词典就要稍微修改下程序。
—————-下面不太重要—————
其中上面第二步中排序用的是如下代码:
/**
* sorts an array.
*
* @param array
* the array to sort
*/
public static void sortArray(int[] array) {
int i, j, h, N = array.length - 1;
for (h = 1; h <= N / 9; h = 3 * h + 1)
;
for (; h > 0; h /= 3) {
for (i = h + 1; i <= N; i++) {
int v = array[i];
j = i;
while (j > h && array[j - h] > v) {
array[j] = array[j - h];
j -= h;
}
array[j] = v;
}
}
}
这个好像是希尔排序,但排序有些小问题,如下测试结果是错误的:
@Test
public void testArray() {
int[] array = {3, 1, 3, 2, 5, 4, 0};
StringToWordVector.sortArray(array);
System.out.println(Arrays.toString(array));
}
结果:[3, 0, 1, 2, 3, 4, 5]
所以大家用这个类时改下这个排序方法。
如果想让最后的向量是tf*idf的值,记得设置filter.setOutputWordCounts(true);