Showing
12 changed files
with
279 additions
and
20 deletions
... | @@ -37,8 +37,7 @@ public class CosineRecommender implements Recommender { | ... | @@ -37,8 +37,7 @@ public class CosineRecommender implements Recommender { |
37 | 37 | ||
38 | ItemRecommender irec; | 38 | ItemRecommender irec; |
39 | 39 | ||
40 | - @Override | 40 | + public CosineRecommender(String filePath) { |
41 | - public void Recommender(String filePath) { | ||
42 | config = new LenskitConfiguration(); | 41 | config = new LenskitConfiguration(); |
43 | 42 | ||
44 | config.bind(ItemScorer.class).to(ItemItemScorer.class); | 43 | config.bind(ItemScorer.class).to(ItemItemScorer.class); | ... | ... |
... | @@ -33,8 +33,7 @@ public class FunkSVDRecommender implements Recommender { | ... | @@ -33,8 +33,7 @@ public class FunkSVDRecommender implements Recommender { |
33 | 33 | ||
34 | ItemRecommender irec; | 34 | ItemRecommender irec; |
35 | 35 | ||
36 | - @Override | 36 | + public FunkSVDRecommender(String filePath) { |
37 | - public void Recommender(String filePath) { | ||
38 | config = new LenskitConfiguration(); | 37 | config = new LenskitConfiguration(); |
39 | 38 | ||
40 | config.bind(ItemScorer.class).to(FunkSVDItemScorer.class); | 39 | config.bind(ItemScorer.class).to(FunkSVDItemScorer.class); | ... | ... |
... | @@ -37,8 +37,7 @@ public class PearsonRecommender implements Recommender { | ... | @@ -37,8 +37,7 @@ public class PearsonRecommender implements Recommender { |
37 | 37 | ||
38 | ItemRecommender irec; | 38 | ItemRecommender irec; |
39 | 39 | ||
40 | - @Override | 40 | + public PearsonRecommender(String filePath) { |
41 | - public void Recommender(String filePath) { | ||
42 | config = new LenskitConfiguration(); | 41 | config = new LenskitConfiguration(); |
43 | 42 | ||
44 | config.bind(ItemScorer.class).to(ItemItemScorer.class); | 43 | config.bind(ItemScorer.class).to(ItemItemScorer.class); | ... | ... |
... | @@ -10,18 +10,8 @@ import org.springframework.context.annotation.Bean; | ... | @@ -10,18 +10,8 @@ import org.springframework.context.annotation.Bean; |
10 | @SpringBootApplication | 10 | @SpringBootApplication |
11 | public class ProcessingApplication { | 11 | public class ProcessingApplication { |
12 | 12 | ||
13 | - Logger logger = LoggerFactory.getLogger(getClass()); | ||
14 | - | ||
15 | public static void main(String[] args) { | 13 | public static void main(String[] args) { |
16 | SpringApplication.run(ProcessingApplication.class, args); | 14 | SpringApplication.run(ProcessingApplication.class, args); |
17 | } | 15 | } |
18 | 16 | ||
19 | - @Bean | ||
20 | - public CommandLineRunner process() { | ||
21 | - return (args) -> this.run(); | ||
22 | - } | ||
23 | - | ||
24 | - public void run() { | ||
25 | - | ||
26 | - } | ||
27 | } | 17 | } | ... | ... |
1 | +package org.legrog.recommendation.process; | ||
2 | + | ||
3 | +import org.grouplens.lenskit.scored.ScoredId; | ||
4 | +import org.slf4j.Logger; | ||
5 | +import org.slf4j.LoggerFactory; | ||
6 | + | ||
7 | +import java.util.List; | ||
8 | +import java.util.Set; | ||
9 | + | ||
10 | +public class ProcessingExpert { | ||
11 | + Logger logger = LoggerFactory.getLogger(getClass()); | ||
12 | + | ||
13 | + Recommender recommender; | ||
14 | + int topSize; | ||
15 | + | ||
16 | + public ProcessingExpert(Recommender recommender, int topSize) { | ||
17 | + this.recommender = recommender; | ||
18 | + this.topSize = topSize; | ||
19 | + } | ||
20 | + | ||
21 | + public ProcessingRecommendations getRecommendations(Set<Long> userIds) { | ||
22 | + ProcessingRecommendations processingRecommendations = new ProcessingRecommendations(); | ||
23 | + List<ScoredId> recommendations; | ||
24 | + for (Long userId : userIds) { | ||
25 | + recommendations = recommender.recommend(userId, topSize); | ||
26 | + for (ScoredId recommendation : recommendations) { | ||
27 | + processingRecommendations.addRecommentation(new RecommendationElement(userId, recommendation.getId())); | ||
28 | + logger.trace("Recommending {} for {}", recommendation.getId(), userId); | ||
29 | + } | ||
30 | + } | ||
31 | + | ||
32 | + return processingRecommendations; | ||
33 | + } | ||
34 | +} |
1 | +package org.legrog.recommendation.process; | ||
2 | + | ||
3 | +import java.util.HashSet; | ||
4 | +import java.util.Set; | ||
5 | + | ||
6 | +public class ProcessingRecommendations { | ||
7 | + Set<RecommendationElement> recommendations; | ||
8 | + | ||
9 | + public ProcessingRecommendations() { | ||
10 | + recommendations = new HashSet<>(); | ||
11 | + } | ||
12 | + | ||
13 | + public void addRecommentation(RecommendationElement recommendationElement) { | ||
14 | + recommendations.add(recommendationElement); | ||
15 | + } | ||
16 | + | ||
17 | + public Set<RecommendationElement> getRecommendations() { | ||
18 | + return recommendations; | ||
19 | + } | ||
20 | +} |
1 | +package org.legrog.recommendation.process; | ||
2 | + | ||
3 | +import org.apache.commons.csv.CSVFormat; | ||
4 | +import org.apache.commons.csv.CSVPrinter; | ||
5 | +import org.apache.commons.csv.CSVRecord; | ||
6 | +import org.slf4j.Logger; | ||
7 | +import org.slf4j.LoggerFactory; | ||
8 | +import org.springframework.beans.factory.annotation.Value; | ||
9 | +import org.springframework.boot.ApplicationArguments; | ||
10 | +import org.springframework.boot.ApplicationRunner; | ||
11 | +import org.springframework.stereotype.Component; | ||
12 | + | ||
13 | +import java.io.*; | ||
14 | +import java.util.HashSet; | ||
15 | +import java.util.Properties; | ||
16 | +import java.util.Set; | ||
17 | + | ||
18 | +@Component | ||
19 | +public class ProcessingRunner implements ApplicationRunner { | ||
20 | + Logger logger = LoggerFactory.getLogger(getClass()); | ||
21 | + | ||
22 | + | ||
23 | + @Value("${parameters.filename}") | ||
24 | + private String parametersFilename; | ||
25 | + | ||
26 | + @Value("${data.dir}") | ||
27 | + private String dataDir; | ||
28 | + | ||
29 | + @Value("${collectionSample.filename}") | ||
30 | + private String collectionSampleFilename; | ||
31 | + | ||
32 | + @Value("${ratingSample.filename}") | ||
33 | + private String ratingSampleFilename; | ||
34 | + | ||
35 | + @Value("${recommandations.filename}") | ||
36 | + private String recommandationsFilename; | ||
37 | + | ||
38 | + private String sampleFilename; | ||
39 | + private String algorithm; | ||
40 | + | ||
41 | + private Recommender recommender; | ||
42 | + | ||
43 | + private int topSize; | ||
44 | + | ||
45 | + @Override | ||
46 | + public void run(ApplicationArguments args) throws Exception { | ||
47 | + loadParameters(); | ||
48 | + logger.trace("Parameters loaded"); | ||
49 | + Set<Long> userIds = loadUserIdsFromSample(); | ||
50 | + RecommenderFactory recommenderFactory = new RecommenderFactory(); | ||
51 | + recommender = recommenderFactory.build(algorithm ,dataDir+sampleFilename); | ||
52 | + logger.trace("Recommender built"); | ||
53 | + ProcessingExpert processingExpert = new ProcessingExpert(recommender, topSize); | ||
54 | + ProcessingRecommendations processingRecommendations = processingExpert.getRecommendations(userIds); | ||
55 | + logger.trace("Recommendations done"); | ||
56 | + writeRecommendationsToFile(processingRecommendations); | ||
57 | + logger.trace("Recommendations written"); | ||
58 | + } | ||
59 | + | ||
60 | + private Set<Long> loadUserIdsFromSample() throws ProcessingException { | ||
61 | + Set<Long> userIds = new HashSet<>(); | ||
62 | + | ||
63 | + Reader in = null; | ||
64 | + try { | ||
65 | + in = new FileReader(dataDir+sampleFilename); | ||
66 | + Iterable<CSVRecord> records = CSVFormat.TDF.withFirstRecordAsHeader().parse(in); | ||
67 | + for (CSVRecord record : records) { | ||
68 | + userIds.add(Long.parseLong(record.get("userId"))); | ||
69 | + } | ||
70 | + } catch (FileNotFoundException e) { | ||
71 | + throw new ProcessingException("sample file not found " + dataDir + sampleFilename, e); | ||
72 | + } catch (IOException e) { | ||
73 | + throw new ProcessingException("Can't read user ids from sample file " + dataDir + sampleFilename, e); | ||
74 | + } | ||
75 | + | ||
76 | + return userIds; | ||
77 | + } | ||
78 | + | ||
79 | + private void writeRecommendationsToFile(ProcessingRecommendations processingRecommendations) throws ProcessingException { | ||
80 | + try { | ||
81 | + CSVPrinter csvPrinter = new CSVPrinter(new FileWriter(new File(dataDir, recommandationsFilename)), | ||
82 | + CSVFormat.TDF.withHeader("itemId", "userId")); | ||
83 | + // TODO : finish | ||
84 | + Set<RecommendationElement> recommendations = processingRecommendations.getRecommendations(); | ||
85 | + if (recommendations.isEmpty()) { | ||
86 | + logger.trace("No recommendations at all"); | ||
87 | + } else { | ||
88 | + logger.trace("{} recommendations", recommendations.size()); | ||
89 | + } | ||
90 | + // forEach incompatible avec IOException | ||
91 | + for (RecommendationElement recommendation : recommendations) { | ||
92 | + csvPrinter.printRecord(recommendation.getItemId(), recommendation.getUserId()); | ||
93 | + } | ||
94 | + csvPrinter.close(); | ||
95 | + } catch (IOException e) { | ||
96 | + throw new ProcessingException("Can't write recommendations file " + dataDir + recommandationsFilename, e); | ||
97 | + } | ||
98 | + } | ||
99 | + | ||
100 | + /** | ||
101 | + * read properties file from application.properties' parameter.fileName then search for parameters inside | ||
102 | + * for ratings, select rating or collection file as the sample file for sample ; using others directly | ||
103 | + * | ||
104 | + * todo replace this by a command line switch ? | ||
105 | + * | ||
106 | + * @throws ProcessingException | ||
107 | + */ | ||
108 | + private void loadParameters() throws ProcessingException { | ||
109 | + try (InputStream in = new FileInputStream(new File(dataDir, parametersFilename))) { | ||
110 | + Properties properties = new Properties(); | ||
111 | + properties.load(in); | ||
112 | + if (properties.containsKey("ratings")) { | ||
113 | + logger.trace("ratings {}", properties.getProperty("ratings")); | ||
114 | + if (Boolean.parseBoolean(properties.getProperty("ratings"))) { | ||
115 | + sampleFilename = ratingSampleFilename; | ||
116 | + } else { | ||
117 | + sampleFilename = collectionSampleFilename; | ||
118 | + } | ||
119 | + } else { | ||
120 | + // by default, takes collection | ||
121 | + sampleFilename = collectionSampleFilename; | ||
122 | + } | ||
123 | + if (properties.containsKey("algorithm")) { | ||
124 | + algorithm = properties.getProperty("algorithm"); | ||
125 | + } else { | ||
126 | + // default algorithm is Cosine | ||
127 | + algorithm = "cosine"; | ||
128 | + } | ||
129 | + if (properties.containsKey("topSize")) { | ||
130 | + topSize = Integer.parseInt(properties.getProperty("topSize")); | ||
131 | + } else { | ||
132 | + // default top size is 10 | ||
133 | + topSize = 10; | ||
134 | + } | ||
135 | + } catch (IOException e) { | ||
136 | + throw new ProcessingException("Can't read parameters properties file " + parametersFilename, e); | ||
137 | + } | ||
138 | + } | ||
139 | + | ||
140 | + private class ProcessingException extends Exception { | ||
141 | + public ProcessingException() { | ||
142 | + super(); | ||
143 | + } | ||
144 | + | ||
145 | + public ProcessingException(String message) { | ||
146 | + super(message); | ||
147 | + } | ||
148 | + | ||
149 | + public ProcessingException(String message, Throwable cause) { | ||
150 | + super(message, cause); | ||
151 | + } | ||
152 | + | ||
153 | + public ProcessingException(Throwable cause) { | ||
154 | + super(cause); | ||
155 | + } | ||
156 | + | ||
157 | + protected ProcessingException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) { | ||
158 | + super(message, cause, enableSuppression, writableStackTrace); | ||
159 | + } | ||
160 | + } | ||
161 | +} | ||
... | \ No newline at end of file | ... | \ No newline at end of file |
1 | +package org.legrog.recommendation.process; | ||
2 | + | ||
3 | +public class RecommendationElement { | ||
4 | + private Long userId; | ||
5 | + private Long itemId; | ||
6 | + | ||
7 | + public RecommendationElement(Long userId, Long itemId) { | ||
8 | + this.userId = userId; | ||
9 | + this.itemId = itemId; | ||
10 | + } | ||
11 | + | ||
12 | + public Long getUserId() { | ||
13 | + return userId; | ||
14 | + } | ||
15 | + | ||
16 | + public Long getItemId() { | ||
17 | + return itemId; | ||
18 | + } | ||
19 | +} |
... | @@ -5,7 +5,5 @@ import org.grouplens.lenskit.scored.ScoredId; | ... | @@ -5,7 +5,5 @@ import org.grouplens.lenskit.scored.ScoredId; |
5 | import java.util.List; | 5 | import java.util.List; |
6 | 6 | ||
7 | public interface Recommender { | 7 | public interface Recommender { |
8 | - | ||
9 | - void Recommender(String filePath); | ||
10 | List<ScoredId> recommend(long user, int n); | 8 | List<ScoredId> recommend(long user, int n); |
11 | } | 9 | } | ... | ... |
1 | +package org.legrog.recommendation.process; | ||
2 | + | ||
3 | +import org.springframework.stereotype.Component; | ||
4 | + | ||
5 | +@Component | ||
6 | +public class RecommenderFactory { | ||
7 | + | ||
8 | + public Recommender build(String recommenderName, String filePath) { | ||
9 | + Recommender recommender; | ||
10 | + | ||
11 | + if (recommenderName.equals("cosine")) { | ||
12 | + recommender = new CosineRecommender(filePath); | ||
13 | + } else if (recommenderName.equals("funkSVD")) { | ||
14 | + recommender = new FunkSVDRecommender(filePath); | ||
15 | + } else if (recommenderName.equals("pearson")) { | ||
16 | + recommender = new PearsonRecommender(filePath); | ||
17 | + } else { | ||
18 | + recommender = new SlopeOneRecommender(filePath); | ||
19 | + } | ||
20 | + | ||
21 | + return recommender; | ||
22 | + } | ||
23 | +} |
... | @@ -30,8 +30,7 @@ public class SlopeOneRecommender implements Recommender { | ... | @@ -30,8 +30,7 @@ public class SlopeOneRecommender implements Recommender { |
30 | 30 | ||
31 | ItemRecommender irec; | 31 | ItemRecommender irec; |
32 | 32 | ||
33 | - @Override | 33 | + public SlopeOneRecommender(String filePath) { |
34 | - public void Recommender(String filePath) { | ||
35 | config = new LenskitConfiguration(); | 34 | config = new LenskitConfiguration(); |
36 | 35 | ||
37 | config.bind(ItemScorer.class).to(SlopeOneItemScorer.class); | 36 | config.bind(ItemScorer.class).to(SlopeOneItemScorer.class); | ... | ... |
1 | +<?xml version="1.0" encoding="UTF-8"?> | ||
2 | +<configuration> | ||
3 | + | ||
4 | + <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender"> | ||
5 | + <!-- encoders are assigned the type | ||
6 | + ch.qos.logback.classic.encoder.PatternLayoutEncoder by default --> | ||
7 | + <encoder> | ||
8 | + <pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern> | ||
9 | + </encoder> | ||
10 | + </appender> | ||
11 | + | ||
12 | + <logger name="org.legrog" level="DEBUG"/> | ||
13 | + <logger name="org.legrog.recommendation.process" level="TRACE"/> | ||
14 | + | ||
15 | + <root level="warn"> | ||
16 | + <appender-ref ref="STDOUT" /> | ||
17 | + </root> | ||
18 | +</configuration> | ||
... | \ No newline at end of file | ... | \ No newline at end of file |
-
Please register or login to post a comment